自底向上的动态规划
方法一:暴搜
1 2 3 4 5 6 7 8 9 10 11 class Solution { public int fib (int n) { return dfs(n); } private int dfs (int index) { if (index == 0 || index == 1 ) return index; return dfs(index - 1 ) + dfs(index - 2 ); } }
方法二:记忆化搜索
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public int fib (int n) { dp = new int [n + 1 ]; return dfs(n); } int [] dp; private int dfs (int index) { if (index == 0 || index == 1 ) return index; if (dp[index] != 0 ) return dp[index]; return dp[index] = dfs(index - 1 ) + dfs(index - 2 ); } }
方法三:DP
1 2 3 4 5 6 7 8 9 10 11 class Solution { public int fib (int n) { if (n == 0 || n == 1 ) return n; int [] dp = new int [n + 1 ]; dp[1 ] = 1 ; for (int i = 2 ; i <= n; ++i) dp[i] = dp[i - 2 ] + dp[i - 1 ]; return dp[n]; } }
方法四:DP + 滚动数组
1 2 3 4 5 6 7 8 9 10 11 class Solution { public int fib (int n) { if (n == 0 || n == 1 ) return n; int [] dp = new int [3 ]; dp[1 ] = 1 ; for (int i = 2 ; i <= n; ++i) dp[i % 3 ] = dp[(i - 2 ) % 3 ] + dp[(i - 1 ) % 3 ]; return dp[n % 3 ]; } }
方法一:暴搜
1 2 3 4 5 6 7 8 9 10 class Solution { public int rob (int [] nums) { return dfs(nums, nums.length - 1 ); } private int dfs (int [] nums, int index) { if (index == 0 || index < 0 ) return index == 0 ? nums[0 ] : 0 ; return Math.max(dfs(nums, index - 2 ) + nums[index], dfs(nums, index - 1 )); } }
方法二:记忆化搜索
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { int [] dp; public int rob (int [] nums) { dp = new int [nums.length]; Arrays.fill(dp, -1 ); return dfs(nums, nums.length - 1 ); } private int dfs (int [] nums, int index) { if (index == 0 || index < 0 ) return index == 0 ? nums[0 ] : 0 ; if (dp[index] != -1 ) return dp[index]; return dp[index] = Math.max(dfs(nums, index - 2 ) + nums[index], dfs(nums, index - 1 )); } }
方法三:DP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public int rob (int [] nums) { if (nums.length == 1 ) return nums[0 ]; if (nums.length == 2 ) return Math.max(nums[0 ], nums[1 ]); int [] dp = new int [nums.length]; dp[0 ] = nums[0 ]; dp[1 ] = Math.max(nums[0 ], nums[1 ]); for (int i = 2 ; i < nums.length; ++i) { dp[i] = Math.max(dp[i - 1 ], dp[i - 2 ] + nums[i]); } return dp[nums.length - 1 ]; } }
方法四:DP + 滚动数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public int rob (int [] nums) { if (nums.length == 1 ) return nums[0 ]; if (nums.length == 2 ) return Math.max(nums[0 ], nums[1 ]); int [] dp = new int [3 ]; dp[0 ] = nums[0 ]; dp[1 ] = Math.max(nums[0 ], nums[1 ]); for (int i = 2 ; i < nums.length; ++i) { dp[i % 3 ] = Math.max(dp[(i - 1 ) % 3 ], dp[(i - 2 ) % 3 ] + nums[i]); } return dp[(nums.length - 1 ) % 3 ]; } }
方法一:回溯
回溯一
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public int minPathSum (int [][] grid) { dfs(grid, 0 , 0 , 0 ); return res; } int res = 8000000 ; private void dfs (int [][] grid, int i, int j, int sum) { if (i == grid.length || j == grid[0 ].length) return ; sum += grid[i][j]; if (i == grid.length - 1 && j == grid[0 ].length - 1 ) { res = Math.min(res, sum); return ; } dfs(grid, i + 1 , j, sum); dfs(grid, i, j + 1 , sum); } }
回溯二
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public int minPathSum (int [][] grid) { dfs(grid, 0 , 0 , 0 ); return res; } int res = 8000000 ; private int dfs (int [][] grid, int i, int j, int sum) { if (i == grid.length || j == grid[0 ].length) return 0 ; sum += grid[i][j]; if (i == grid.length - 1 && j == grid[0 ].length - 1 ) { res = Math.min(res, sum); return sum; } return dfs(grid, i + 1 , j, sum) + dfs(grid, i, j + 1 , sum); } }
方法二:记忆化搜索
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public int minPathSum (int [][] grid) { dp = new int [grid.length][grid[0 ].length]; for (int [] arr : dp) Arrays.fill(arr, -1 ); return dfs(grid, 0 , 0 ); } int [][] dp; private int dfs (int [][] grid, int i, int j) { if (i == grid.length || j == grid[0 ].length) return 8000000 ; if (dp[i][j] != -1 ) return dp[i][j]; if (i == grid.length - 1 && j == grid[0 ].length - 1 ) { return grid[i][j]; } return dp[i][j] = grid[i][j] + Math.min(dfs(grid, i + 1 , j), dfs(grid, i, j + 1 )); } }
方法三:DP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public int minPathSum (int [][] grid) { int [][] dp = new int [grid.length][grid[0 ].length]; dp[0 ][0 ] = grid[0 ][0 ]; for (int i = 1 ; i < grid.length; ++i) dp[i][0 ] += dp[i - 1 ][0 ] + grid[i][0 ]; for (int j = 1 ; j < grid[0 ].length; ++j) dp[0 ][j] += dp[0 ][j - 1 ] + grid[0 ][j]; for (int i = 1 ; i < grid.length; ++i) { for (int j = 1 ; j < grid[0 ].length; ++j) { dp[i][j] = Math.min(dp[i - 1 ][j], dp[i][j - 1 ]) + grid[i][j]; } } return dp[grid.length - 1 ][grid[0 ].length - 1 ]; } }
方法一:回溯
1 2 3 4 5 6 7 8 9 10 11 12 class Solution { public int uniquePaths (int m, int n) { return dfs(m, n, 1 , 1 ); } private int dfs (int m, int n, int i, int j) { if (i == m + 1 || j == n + 1 ) return 0 ; if (i == m && j == n) return 1 ; return dfs(m, n, i + 1 , j) + dfs(m, n, i, j + 1 ); } }
方法二:记忆化搜索
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public int uniquePaths (int m, int n) { dp = new int [m + 1 ][n + 1 ]; return dfs(m, n, 1 , 1 ); } int [][] dp; private int dfs (int m, int n, int i, int j) { if (i == m + 1 || j == n + 1 ) return 0 ; if (i == m && j == n) return 1 ; if (dp[i][j] != 0 ) return dp[i][j]; return dp[i][j] = dfs(m, n, i + 1 , j) + dfs(m, n, i, j + 1 ); } }
方法三:组合数学
res = res * i / j不能写成*=,因为可能i / j不为整数
1 2 3 4 5 6 7 8 class Solution { public int uniquePaths (int m, int n) { long res = 1 ; for (int i = n, j = 1 ; j < m; ++i, ++j) res = res * i / j; return (int ) res; } }
方法四:DP
第0行第0列赋值1,反正后面都会被覆盖,干脆全赋值1
1 2 for (int [] arr : dp) Arrays.fill(arr, 1 );
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { public int uniquePaths (int m, int n) { if (m == 1 || n == 1 ) return 1 ; int [][] dp = new int [m][n]; for (int [] arr : dp) Arrays.fill(arr, 1 ); for (int i = 1 ; i < m; ++i) { for (int j = 1 ; j < n; ++j) { dp[i][j] = dp[i - 1 ][j] + dp[i][j - 1 ]; } } return dp[m - 1 ][n - 1 ]; } }
方法五:DP + 滚动数组(两行)
边界都是1,所以可以滚动,不同路径那题不行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { public int uniquePaths (int m, int n) { if (m == 1 || n == 1 ) return 1 ; int [][] dp = new int [2 ][n]; for (int [] arr : dp) Arrays.fill(arr, 1 ); for (int i = 1 ; i < m; ++i) { for (int j = 1 ; j < n; ++j) { dp[i % 2 ][j] = dp[(i - 1 ) % 2 ][j] + dp[i % 2 ][j - 1 ]; } } return dp[(m - 1 ) % 2 ][n - 1 ]; } }
方法五:DP + 滚动数组(一行)
这里把数组全部初始化为1,是为了模拟第0行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { public int uniquePaths (int m, int n) { if (m == 1 || n == 1 ) return 1 ; int [] dp = new int [n]; Arrays.fill(dp, 1 ); for (int i = 1 ; i < m; ++i) { for (int j = 1 ; j < n; ++j) { dp[j] += + dp[j - 1 ]; } } return dp[n - 1 ]; } }
方法一:回溯
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { public int uniquePathsWithObstacles (int [][] obstacleGrid) { int m = obstacleGrid.length, n = obstacleGrid[0 ].length; return dfs(obstacleGrid, m, n, 0 , 0 ); } private int dfs (int [][] obstacleGrid, int m, int n, int i, int j) { if (i == m || j == n || obstacleGrid[i][j] == 1 ) return 0 ; if (i == m - 1 && j == n - 1 ) return 1 ; return dfs(obstacleGrid, m, n, i + 1 , j) + dfs(obstacleGrid, m, n, i, j + 1 ); } }
方法二:记忆化搜索
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public int uniquePathsWithObstacles (int [][] obstacleGrid) { int m = obstacleGrid.length, n = obstacleGrid[0 ].length; dp = new int [m][n]; for (int [] arr : dp) Arrays.fill(arr, -1 ); return dfs(obstacleGrid, m, n, 0 , 0 ); } int [][] dp; private int dfs (int [][] obstacleGrid, int m, int n, int i, int j) { if (i == m || j == n || obstacleGrid[i][j] == 1 ) return 0 ; if (i == m - 1 && j == n - 1 ) return 1 ; if (dp[i][j] != -1 ) return dp[i][j]; return dp[i][j] = dfs(obstacleGrid, m, n, i + 1 , j) + dfs(obstacleGrid, m, n, i, j + 1 ); } }
方法三:DP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution { public int uniquePathsWithObstacles (int [][] obstacleGrid) { int m = obstacleGrid.length, n = obstacleGrid[0 ].length; int [][] dp = new int [m][n]; for (int i = 0 ; i < m; ++i) { if (obstacleGrid[i][0 ] == 1 ) break ; dp[i][0 ] = 1 ; } for (int j = 0 ; j < n; ++j) { if (obstacleGrid[0 ][j] == 1 ) break ; dp[0 ][j] = 1 ; } for (int i = 1 ; i < m; ++i) { for (int j = 1 ; j < n; ++j) { if (obstacleGrid[i][j] == 1 ) continue ; dp[i][j] = dp[i - 1 ][j] + dp[i][j - 1 ]; } } return dp[m - 1 ][n - 1 ]; } }
方法五:DP + 滚动数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public int uniquePathsWithObstacles (int [][] obstacleGrid) { int m = obstacleGrid.length, n = obstacleGrid[0 ].length; int [] dp = new int [n]; dp[0 ] = obstacleGrid[0 ][0 ] == 1 ? 0 : 1 ; for (int i = 0 ; i < m; ++i) { for (int j = 0 ; j < n; ++j) { if (obstacleGrid[i][j] == 1 ) { dp[j] = 0 ; continue ; } if (j >= 1 && obstacleGrid[i][j - 1 ] == 0 ) dp[j] += dp[j - 1 ]; } } return dp[n - 1 ]; } }
方法一:回溯 先放错误版本,debug1个多小时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Solution { public int uniquePathsIII (int [][] grid) { this .grid = grid; m = grid.length; n = grid[0 ].length; int [] oriIndex = null ; int left = 0 ; for (int i = 0 ; i < m; ++i) { for (int j = 0 ; j < n; ++j) { if (grid[i][j] == 0 ) ++left; else if (grid[i][j] == 1 ) oriIndex = new int []{i, j}; } } return dfs(oriIndex[0 ], oriIndex[1 ], left + 1 ); } private int dfs (int i, int j, int left) { if (grid[i][j] == 2 ) return left == 0 ? 1 : 0 ; int res = 0 ; for (int [] dir : dirs) { int row = i + dir[0 ], col = j + dir[1 ]; if (isValid(row, col) && grid[row][col] != -1 ) { int temp1 = grid[row][col]; grid[row][col] = -1 ; res += dfs(row, col, left - 1 ); grid[row][col] = temp1; } } return res; } private boolean isValid (int i, int j) { return i >= 0 && j >= 0 && i < m && j < n; } int [][] grid, dirs = new int [][]{{-1 , 0 }, {1 , 0 }, {0 , -1 }, {0 , 1 }};; int m, n; }
在错误版本中,是将邻居grid[row][col] = -1;置为以访问状态,那么从i ,j递归到 row, col又可以递归回i,j所以left的次数一定是错的
回溯正确版本
将i,j置为以访问状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 class Solution { public int uniquePathsIII (int [][] grid) { this .grid = grid; m = grid.length; n = grid[0 ].length; int [] oriIndex = null ; int left = 0 ; for (int i = 0 ; i < m; ++i) { for (int j = 0 ; j < n; ++j) { if (grid[i][j] == 0 ) ++left; else if (grid[i][j] == 1 ) oriIndex = new int []{i, j}; } } return dfs(oriIndex[0 ], oriIndex[1 ], left + 1 ); } private int dfs (int i, int j, int left) { if (grid[i][j] == 2 ) return left == 0 ? 1 : 0 ; int res = 0 , temp = grid[i][j]; grid[i][j] = -1 ; for (int [] dir : dirs) { int row = i + dir[0 ], col = j + dir[1 ]; if (isValid(row, col) && (grid[row][col] == 0 || grid[row][col] == 2 )) { res += dfs(row, col, left - 1 ); } } grid[i][j] = temp; return res; } private boolean isValid (int i, int j) { return i >= 0 && j >= 0 && i < m && j < n; } int [][] grid, dirs = new int [][]{{-1 , 0 }, {1 , 0 }, {0 , -1 }, {0 , 1 }};; int m, n; }
方法一:DP
最后一行的长度是2n - 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public List<List<Integer>> generate (int numRows) { List<List<Integer>> res = new LinkedList <>(); int [][] dp = new int [numRows][numRows * 2 + 1 ]; dp[0 ][dp[0 ].length / 2 ] = 1 ; res.add(new LinkedList <>(Arrays.asList(1 ))); for (int i = 1 ; i < numRows; ++i) { List<Integer> list = new LinkedList <>(); for (int j = 0 ; j < dp[0 ].length; ++j) { dp[i][j] = (j < dp[0 ].length - 1 ? dp[i - 1 ][j + 1 ] : 0 ) + (j >= 1 ? dp[i - 1 ][j - 1 ] : 0 ); if (dp[i][j] != 0 ) list.add(dp[i][j]); } res.add(list); } return res; } }
方法二:DP
第一列与最后一列是1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public List<List<Integer>> generate (int numRows) { List<List<Integer>> res = new LinkedList <>(); List<Integer> row = new LinkedList <>(); row.add(1 ); res.add(row); for (int i = 2 ; i <= numRows; ++i) { row = new LinkedList <>(); row.add(1 ); for (int j = 1 ; j < i - 1 ; ++j) row.add(res.get(i - 2 ).get(j - 1 ) + res.get(i - 2 ).get(j)); row.add(1 ); res.add(row); } return res; } }
二刷回溯
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public int minimumTotal (List<List<Integer>> triangle) { return dfs(triangle, 0 , 0 ); } private int dfs (List<List<Integer>> triangle, int i, int j) { if (i == triangle.size()) return 0 ; return triangle.get(i).get(j) + Math.min(dfs(triangle, i + 1 , j), dfs(triangle, i + 1 , j + 1 )); } }
二刷记忆化搜索
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution { public int minimumTotal (List<List<Integer>> triangle) { dp = new int [triangle.size()][triangle.size()]; for (int [] arr : dp) Arrays.fill(arr, 10001 ); return dfs(triangle, 0 , 0 ); } int [][] dp; private int dfs (List<List<Integer>> triangle, int i, int j) { if (i == triangle.size()) return 0 ; if (dp[i][j] != 10001 ) return dp[i][j]; return dp[i][j] = triangle.get(i).get(j) + Math.min(dfs(triangle, i + 1 , j), dfs(triangle, i + 1 , j + 1 )); } }
二刷DP
dp数组多创建一列,把dp数组初始化为10001,处理第一列没有左上方元素与最后一列没有正上方元素的特殊情况
1 int [][] dp = new int [triangle.size()][triangle.size() + 1 ];
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public int minimumTotal (List<List<Integer>> triangle) { if (triangle.size() == 1 ) return triangle.get(0 ).get(0 ); int [][] dp = new int [triangle.size()][triangle.size() + 1 ]; int min = 10001 ; for (int [] arr : dp) Arrays.fill(arr, 10001 ); dp[0 ][1 ] = triangle.get(0 ).get(0 ); for (int i = 1 ; i < triangle.size(); ++i) { for (int j = 0 ; j < triangle.get(i).size(); ++j) { dp[i][j + 1 ] = Math.min(dp[i - 1 ][j], dp[i - 1 ][j + 1 ]) + triangle.get(i).get(j); if (i == triangle.size() - 1 ) min = Math.min(min, dp[i][j + 1 ]); } } return min; } }
从最后一行开始遍历
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public int minimumTotal (List<List<Integer>> triangle) { int n = triangle.size(); int [][] dp = new int [n + 1 ][n + 1 ]; for (int i = n - 1 ; i >= 0 ; --i) { for (int j = 0 ; j <= i; ++j) { dp[i][j] = Math.min(dp[i + 1 ][j], dp[i + 1 ][j + 1 ]) + triangle.get(i).get(j); } } return dp[0 ][0 ]; } }
方法一:回溯
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int minimumTotal (List<List<Integer>> triangle) { int n = triangle.size(); int [][] list = new int [n][n]; for (int i = 0 ; i < n; ++i) { for (int j = 0 ; j < i + 1 ; ++j) { list[i][j] = triangle.get(i).get(j); } } return dfs(list, n, 0 , 0 ); } private int dfs (int [][] list, int n, int i, int j) { if (j > i) return 0 ; if (i == n - 1 ) return list[i][j]; return Math.min(dfs(list, n, i + 1 , j), dfs(list, n, i + 1 , j + 1 )) + list[i][j]; } }
方法二:记忆化搜索一
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public int minimumTotal (List<List<Integer>> triangle) { int n = triangle.size(); int [][] list = new int [n][n]; dp = new int [n][n]; for (int [] arr : dp) Arrays.fill(arr, Integer.MAX_VALUE); for (int i = 0 ; i < n; ++i) { for (int j = 0 ; j < i + 1 ; ++j) { list[i][j] = triangle.get(i).get(j); } } return dfs(list, n, 0 , 0 ); } int [][] dp; private int dfs (int [][] list, int n, int i, int j) { if (j > i) return 0 ; if (i == n - 1 ) return list[i][j]; if (dp[i][j] != Integer.MAX_VALUE) return dp[i][j]; return dp[i][j] = Math.min(dfs(list, n, i + 1 , j), dfs(list, n, i + 1 , j + 1 )) + list[i][j]; } }
优化记忆化搜索
只改动了base case
i == n-1就直接返回,会使最后一次dp信息漏掉,如果最后一行很长,那么会多进入dfs很多次
注意最后一行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public int minimumTotal (List<List<Integer>> triangle) { int n = triangle.size(); int [][] list = new int [n][n]; dp = new int [n][n]; for (int [] arr : dp) Arrays.fill(arr, Integer.MAX_VALUE); for (int i = 0 ; i < n; ++i) { for (int j = 0 ; j < i + 1 ; ++j) { list[i][j] = triangle.get(i).get(j); } } return dfs(list, n, 0 , 0 ); } int [][] dp; private int dfs (int [][] list, int n, int i, int j) { if (j > i) return 0 ; if (i == n) return 0 ; if (dp[i][j] != Integer.MAX_VALUE) return dp[i][j]; return dp[i][j] = Math.min(dfs(list, n, i + 1 , j), dfs(list, n, i + 1 , j + 1 )) + list[i][j]; } }
方法三:DP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Solution { public int minimumTotal (List<List<Integer>> triangle) { int n = triangle.size(); int [][] dp = new int [n][n]; for (int [] arr : dp) Arrays.fill(arr, Integer.MAX_VALUE); int res = Integer.MAX_VALUE; for (int i = 0 ; i < n; ++i) { for (int j = 0 ; j < i + 1 ; ++j) { int pre = 0 ; if (i >= 1 ) { pre = dp[i - 1 ][j]; if (j >= 1 ) pre = Math.min(pre, dp[i - 1 ][j - 1 ]); } dp[i][j] = triangle.get(i).get(j) + pre; if (i == n - 1 ) res = Math.min(res, dp[i][j]); } } return res; } }
方法四:DP + 滚动数组
和01背包类似,第二次for循环要倒序遍历;如果正序遍历那么pre = dp[j]拿到的是第i行的数据,我们需要的是第i-1行的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public int minimumTotal (List<List<Integer>> triangle) { int n = triangle.size(); int [] dp = new int [n]; Arrays.fill(dp, 1000000 ); int res = Integer.MAX_VALUE; for (int i = 0 ; i < n; ++i) { for (int j = i; j >= 0 ; --j) { int pre = 0 ; if (i >= 1 ) { pre = dp[j]; if (j >= 1 ) pre = Math.min(pre, dp[j - 1 ]); } dp[j] = triangle.get(i).get(j) + pre; if (i == n - 1 ) res = Math.min(res, dp[j]); } } return res; } }
完全背包问题
方法一:DP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int numSquares (int n) { int m = (int ) Math.sqrt(n); int [] nums = new int [m]; for (int i = 0 ; i < m; ++i) { nums[i] = (i + 1 ) * (i + 1 ); } int [] dp = new int [n + 1 ]; for (int i = 1 ; i < dp.length; ++i) dp[i] = 10001 ; for (int i = 0 ; i < nums.length; ++i) { for (int j = nums[i]; j <= n; ++j) { dp[j] = Math.min(dp[j], dp[j - nums[i]] + 1 ); } } return dp[n]; } }
思路:
排列问题
爬楼梯问题
方法一:DP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public int combinationSum4 (int [] nums, int target) { int [] dp = new int [target + 1 ]; dp[0 ] = 1 ; for (int i = 1 ; i <= target; ++i) { for (int j = 0 ; j < nums.length; ++j) { if (i >= nums[j]) { dp[i] += dp[i - nums[j]]; } } } return dp[target]; } }
如果要求返回所有排列数,如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package 背包;import java.util.*;public class Solution { List<List<Integer>> res = new LinkedList <>(); Deque<Integer> path = new LinkedList <>(); public List<List<Integer>> combinationSum4 (int [] nums, int target) { backtracking(nums, target, 0 ); return res; } private void backtracking (int [] nums, int target, int index) { if (target == 0 ) { res.add(new LinkedList <>(path)); return ; } for (int i = index; i < nums.length; ++i) { if (target < nums[i]) break ; path.offerLast(nums[i]); backtracking(nums, target - nums[i], index); path.pollLast(); } } public static void main (String[] args) { Solution solution = new Solution (); System.out.println(solution.combinationSum4(new int []{1 , 2 , 3 }, 4 )); } }
方法一:DP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public int lengthOfLIS (int [] nums) { int res = 1 ; int [] dp = new int [nums.length]; Arrays.fill(dp, 1 ); for (int i = 0 ; i < nums.length; ++i) { for (int j = i + 1 ; j < nums.length; ++j) { if (nums[j] > nums[i]) { dp[j] = Math.max(dp[j], dp[i] + 1 ); } res = Math.max(res, dp[j]); } } return res; } }
二刷DP:从前往后
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public int lengthOfLIS (int [] nums) { if (nums.length == 1 ) return 1 ; int [] dp = new int [nums.length]; Arrays.fill(dp, 1 ); int res = 1 ; for (int i = 1 ; i < nums.length; ++i) { for (int j = 0 ; j < i; ++j) { if (nums[j] < nums[i]) { dp[i] = Math.max(dp[i], dp[j] + 1 ); res = Math.max(res, dp[i]); } } } return res; } }
方法二:贪心 + 二分
方法一:暴力
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public int findLengthOfLCIS (int [] nums) { int res = 1 ; for (int i = 0 ; i < nums.length; ++i) { int length = 1 ; for (int j = i + 1 ; j < nums.length; ++j) { if (nums[j] > nums[j - 1 ]) { ++length; res = Math.max(res, length); } else { length = 1 ; } } } return res; } }
方法二:DP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public int findLengthOfLCIS (int [] nums) { int [] dp = new int [nums.length]; Arrays.fill(dp, 1 ); int res = 1 ; for (int i = 1 ; i < nums.length; ++i) { if (nums[i] > nums[i - 1 ]) { dp[i] = dp[i - 1 ] + 1 ; res = Math.max(res, dp[i]); } } return res; } }
方法三:DP + 滚动数组
如果nums[i] <= nums[i-1],记得dp[i % 2] = 1;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int findLengthOfLCIS (int [] nums) { int [] dp = new int [2 ]; Arrays.fill(dp, 1 ); int res = 1 ; for (int i = 1 ; i < nums.length; ++i) { if (nums[i] > nums[i - 1 ]) { dp[i % 2 ] = dp[(i - 1 ) % 2 ] + 1 ; res = Math.max(res, dp[i % 2 ]); } else { dp[i % 2 ] = 1 ; } } return res; } }
动动脑子吧你
方法一:暴力
时间复杂度:\(O(m^2n)\)
空间复杂度:\(O(1)\)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public int findLength (int [] nums1, int [] nums2) { int res = 0 ; for (int i = 0 ; i < nums1.length; ++i) { for (int j = 0 ; j < nums2.length; ++j) { int count1 = i, count2 = j; int counter = 0 ; while (count1 < nums1.length && count2 < nums2.length && nums1[count1] == nums2[count2]) { ++counter; ++count1; ++count2; } res = Math.max(res, counter); } } return res; } }
方法二:DP
dp[i][j]:以nums1[i - 1]结尾与以nums2[j - 1]结尾时的最长重复子数组
状态转移公式:当nums1[i - 1] == nums2[j - 1]时,dp[i][j] = dp[i - 1][j - 1] + 1
初始化:m为nums1的长度,n为nums2的长度,初始化(m + 1) * (n + 1)的二维数组,第一行第一列初始化为0,因为任一数组为空,就不会有重复子数组
注意事项:输出结果是dp数组中的最大值,因为最长的重复子数组不一定是在两数组的结尾处,可以在循环内比较
时间复杂度:\(O(mn)\)
空间复杂度:\(O(mn)\)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public int findLength (int [] nums1, int [] nums2) { int m = nums1.length, n = nums2.length; int [][] dp = new int [m + 1 ][n + 1 ]; int result = 0 ; for (int i = 1 ; i <= m; ++i) { for (int j = 1 ; j <= n; ++j) { if (nums1[i - 1 ] == nums2[j - 1 ]) { dp[i][j] = dp[i - 1 ][j - 1 ] + 1 ; result = Math.max(result, dp[i][j]); } } } return result; } }
滚动数组缩减空间复杂度
dp数组为第二个数组的长度加一
不相等时要把dp[j]赋0,不然之后会出错
此时遍历第二个数组的时候,就要从后向前遍历,这样避免重复覆盖 。
比如nums1 = [1, 2, 3, 1],nums2 = [1,6,8,1,3,1,2,3]
i = 1时候,dp数组为[1,0,0,1,0,1,0,0]
i = 2时候,dp数组为[0,0,0,0,0,0,1,0]
i = 3时候,dp数组为[0,0,0,0,1,0,0,2]
i=3时,如果第二层for循环j从前往后遍历,那么dp倒数第二个位置会因为3!=2,被赋0,进而导致dp最后一个位置是1而不是2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public int findLength (int [] nums1, int [] nums2) { int [] dp = new int [nums2.length + 1 ]; int res = 0 ; for (int i = 1 ; i <= nums1.length; ++i) { for (int j = nums2.length; j > 0 ; --j) { if (nums1[i - 1 ] == nums2[j - 1 ]) { dp[j] = dp[j - 1 ] + 1 ; } else { dp[j] = 0 ; } res = Math.max(res, dp[j]); } } return res; } }
方法三:滚动数组(一行)
用pre记录左上角的值
计算当前值之前,先用temp保存dp[j + 1],计算完dp[j + 1],将temp赋值给pre
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Solution { public int longestCommonSubsequence (String s, String t) { int m = s.length(), n = t.length(); int [] dp = new int [n + 1 ]; for (int i = 0 ; i < m; ++i) { int pre = 0 ; for (int j = 0 ; j < n; ++j) { int temp = dp[j + 1 ]; if (s.charAt(i) == t.charAt(j)) dp[j + 1 ] = pre + 1 ; else dp[j + 1 ] = Math.max(dp[j], dp[j + 1 ]); pre = temp; } } return dp[n]; } }
方法一:DP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public int longestCommonSubsequence (String text1, String text2) { int [][] dp = new int [text1.length() + 1 ][text2.length() + 1 ]; for (int i = 1 ; i <= text1.length(); ++i) { for (int j = 1 ; j <= text2.length(); ++j) { if (text1.charAt(i - 1 ) == text2.charAt(j - 1 )) dp[i][j] = dp[i - 1 ][j - 1 ] + 1 ; else dp[i][j] = Math.max(dp[i - 1 ][j], dp[i][j - 1 ]); } } return dp[text1.length()][text2.length()]; } }
方法二:DP + 滚动数组(两行)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public int longestCommonSubsequence (String text1, String text2) { int [][] dp = new int [2 ][text2.length() + 1 ]; for (int i = 1 ; i <= text1.length(); ++i) { for (int j = 1 ; j <= text2.length(); ++j) { if (text1.charAt(i - 1 ) == text2.charAt(j - 1 )) { dp[i % 2 ][j] = dp[(i - 1 ) % 2 ][j - 1 ] + 1 ; } else { dp[i % 2 ][j] = Math.max(dp[i % 2 ][j - 1 ], dp[(i - 1 ) % 2 ][j]); } } } return dp[text1.length() % 2 ][text2.length()]; } }
方法一:最长公共子序列
代码和最长公共子序列一模一样,只是返回值不同
找出最长公共子序列长度为x,然后用word1的长度 + word2的长度 - x
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int minDistance (String text1, String text2) { int [][] dp = new int [2 ][text2.length() + 1 ]; for (int i = 1 ; i <= text1.length(); ++i) { for (int j = 1 ; j <= text2.length(); ++j) { if (text1.charAt(i - 1 ) == text2.charAt(j - 1 )) { dp[i % 2 ][j] = dp[(i - 1 ) % 2 ][j - 1 ] + 1 ; } else { dp[i % 2 ][j] = Math.max(dp[i % 2 ][j - 1 ], dp[(i - 1 ) % 2 ][j]); } } } return text1.length() + text2.length() - dp[text1.length() % 2 ][text2.length()] * 2 ; } }
方法二:DP
dp[i][j]:使得以i为结尾的子串sub1与以j为结尾的子串sub2相同所需的最少删除次数
m为word1的长度,n为word2的长度,创建大小为(m + 1) * (n + 1)大小的dp数组,首先需要初始化第0行与第0列,初始化第0行表示使为空字符串的sub1与长度为j的子串sub2相同所需的最少删除次数,举例word1 = “sea”,word2 = “eeat”,空字符串与空字符串:0,空字符串与“e”:1,空字符串与“ee”:2,空字符串与“eea”:3,空字符串与“eeat”:4。
初始化第0列类似。以下为初始化dp数组
0
1
2
3
4
1
0
0
0
0
2
0
0
0
0
3
0
0
0
0
状态转移方程:
如果两个字符i、j相同,那么所需删除的次数和没加入这两个字符所需要的次数一致
1 2 if (word1.charAt(i - 1 ) == word2.charAt(j - 1 )) dp[i][j] = dp[i - 1 ][j - 1 ];
如果两个字符i、j不同,所需删除的次数为如下
1 dp[i][j] = Math.min(dp[i - 1 ][j], dp[i][j - 1 ]) + 1 ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public int minDistance (String word1, String word2) { int m = word1.length(), n = word2.length(); int [][] dp = new int [m + 1 ][n + 1 ]; for (int j = 0 ; j <= n; ++j) dp[0 ][j] = j; for (int i = 0 ; i <= m; ++i) dp[i][0 ] = i; for (int i = 1 ; i <= m; ++i) { for (int j = 1 ; j <= n; ++j) { if (word1.charAt(i - 1 ) == word2.charAt(j - 1 )) dp[i][j] = dp[i - 1 ][j - 1 ]; else dp[i][j] = Math.min(dp[i - 1 ][j], dp[i][j - 1 ]) + 1 ; } } return dp[m][n]; } }
优质题解
方法一:DP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int minDistance (String word1, String word2) { int m = word1.length(), n = word2.length(); int [][] dp = new int [m + 1 ][n + 1 ]; for (int i = 1 ; i <= m; ++i) dp[i][0 ] = dp[i - 1 ][0 ] + 1 ; for (int j = 1 ; j <= n; ++j) dp[0 ][j] = dp[0 ][j - 1 ] + 1 ; for (int i = 1 ; i <= m; ++i) { char ch1 = word1.charAt(i - 1 ); for (int j = 1 ; j <= n; ++j) { if (ch1 != word2.charAt(j - 1 )) dp[i][j] = Math.min(Math.min(dp[i - 1 ][j - 1 ], dp[i - 1 ][j]), dp[i][j - 1 ]) + 1 ; else dp[i][j] = dp[i - 1 ][j - 1 ]; } } return dp[m][n]; } }
二刷DP
当 word1[i] == word2[j],dp[i][j] = dp[i-1][j-1];
当 word1[i] != word2[j],dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1
其中,dp[i-1][j-1] 表示替换操作,dp[i-1][j] 表示删除操作,dp[i][j-1] 表示插入操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public int minDistance (String word1, String word2) { int m = word1.length(), n = word2.length(); int [][] dp = new int [m + 1 ][n + 1 ]; for (int j = 1 ; j <= n; ++j) dp[0 ][j] = j; for (int i = 1 ; i <= m; ++i) dp[i][0 ] = i; for (int i = 1 ; i <= m; ++i) { for (int j = 1 ; j <= n; ++j) { if (word1.charAt(i - 1 ) == word2.charAt(j - 1 )) dp[i][j] = dp[i - 1 ][j - 1 ]; else dp[i][j] = Math.min(Math.min(dp[i - 1 ][j], dp[i][j - 1 ]), dp[i - 1 ][j - 1 ]) + 1 ; } } return dp[m][n]; } }
优质题解
二刷看的题解
状态:dp[i][j] 表示字符串s在[i,j]区间的子串是否是一个回文串。 状态转移方程:当 s[i] == s[j] && (j - i < 2 || dp[i + 1][j - 1]) 时,dp[i][j]=true,否则为false 这个状态转移方程是什么意思呢?
当只有一个字符时,比如 a 自然是一个回文串。 当有两个字符时,如果是相等的,比如 aa,也是一个回文串。 当有三个及以上字符时,比如 ababa 这个字符记作串 1,把两边的 a 去掉,也就是 bab 记作串 2,可以看出只要串2是一个回文串,那么左右各多了一个 a 的串 1 必定也是回文串。所以当 s[i]==s[j] 时,自然要看 dp[i+1][j-1] 是不是一个回文串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { public int countSubstrings (String s) { int n = s.length(), res = 0 ; boolean [][] dp = new boolean [n][n]; for (int j = 0 ; j < n; ++j) for (int i = 0 ; i <= j; ++i) if (s.charAt(i) == s.charAt(j) && (j - i <= 1 || dp[i + 1 ][j - 1 ])) { dp[i][j] = true ; ++res; } return res; } }
换一种遍历顺序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Solution { public int countSubstrings (String s) { int n = s.length(), res = 0 ; boolean [][] dp = new boolean [n][n]; for (int i = n - 1 ; i >= 0 ; --i) for (int j = i; j < n; ++j) if (s.charAt(i) == s.charAt(j) && (j - i <= 1 || dp[i + 1 ][j - 1 ])) { dp[i][j] = true ; ++res; } return res; } }
方法一:DP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int countSubstrings (String s) { int n = s.length(), res = 0 ; boolean [][] dp = new boolean [n][n]; for (int j = 0 ; j < n; ++j) { for (int i = j; i >= 0 ; --i) { if (i == j) dp[i][j] = true ; else if (i + 1 == j) dp[i][j] = s.charAt(i) == s.charAt(j) ? true : false ; else { dp[i][j] = s.charAt(i) == s.charAt(j) && dp[i + 1 ][j - 1 ]; } if (dp[i][j]) ++res; } } return res; } }
方法一:DP
dp[i][j] = ch1 == ch2 && (dp[i + 1][j - 1] || j - i + 1 <= 2) 从最后一行开始遍历,从左往右、从右往左都可以
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public String longestPalindrome (String s) { int n = s.length(),l = 0 , r = 0 , maxLen = 0 ; boolean [][] dp = new boolean [n][n]; for (int i = n - 1 ; i >= 0 ; --i) { for (int j = i; j < n; ++j) { if (s.charAt(i) == s.charAt(j) && (j - i + 1 <= 2 || dp[i + 1 ][j - 1 ])) { dp[i][j] = true ; if (j - i + 1 > maxLen) { maxLen = j - i + 1 ; l = i; r = j; } } } } return s.substring(l, r + 1 ); } }
方法一:DP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public int longestPalindromeSubseq (String s) { int n = s.length(); int [][] dp = new int [n][n]; for (int j = 0 ; j < n; ++j) { for (int i = j; i >= 0 ; --i) { if (i == j) dp[i][j] = 1 ; else if (i + 1 == j) dp[i][j] = s.charAt(i) == s.charAt(j) ? 2 : 1 ; else { if (s.charAt(i) == s.charAt(j)) dp[i][j] = dp[i + 1 ][j - 1 ] + 2 ; else dp[i][j] = Math.max(dp[i + 1 ][j], dp[i][j - 1 ]); } } } return dp[0 ][n - 1 ]; } }
二刷
dp[i + 1][j - 1]
dp[i + 1][j]
从下往上,从左往右遍历
else if (s.charAt(i) == s.charAt(j))一定要else if不能是if。如果是if会越界(i = n - 1时, dp[i + 1][j - 1]越界)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public int longestPalindromeSubseq (String s) { int n = s.length(); int [][] dp = new int [n][n]; for (int i = n - 1 ; i >= 0 ; --i) { for (int j = i; j < n; ++j) { if (i == j) dp[i][j] = 1 ; else if (s.charAt(i) == s.charAt(j)) dp[i][j] = dp[i + 1 ][j - 1 ] + 2 ; else dp[i][j] = Math.max(dp[i + 1 ][j], dp[i][j - 1 ]); } } return dp[0 ][n - 1 ]; } }
剑指 Offer II 092. 翻转字符
注意:(ch == '1' ? 0 : 1)的括号
方法一:DP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Solution { public int minFlipsMonoIncr (String s) { int [][] dp = new int [s.length()][2 ]; dp[0 ][0 ] = s.charAt(0 ) == '0' ? 0 : 1 ; dp[0 ][1 ] = s.charAt(0 ) == '1' ? 0 : 1 ; for (int i = 1 ; i < s.length(); ++i) { char ch = s.charAt(i); dp[i][0 ] = dp[i - 1 ][0 ] + (ch == '0' ? 0 : 1 ); dp[i][1 ] = Math.min(dp[i - 1 ][0 ], dp[i - 1 ][1 ]) + (ch == '1' ? 0 : 1 ); } return Math.min(dp[s.length() - 1 ][0 ], dp[s.length() - 1 ][1 ]); } }
剑指 Offer II 093. 最长斐波那契数列
方法一:DP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public int lenLongestFibSubseq (int [] arr) { int n = arr.length; int [][] dp = new int [n][n]; Map<Integer, Integer> map = new HashMap <>(); int res = 0 ; for (int i = 0 ; i < n; ++i) map.put(arr[i], i); for (int i = 2 ; i < n; ++i) { for (int j = i - 1 ; j >= 0 ; --j) { int k = map.getOrDefault(arr[i] - arr[j], -1 ); if (k >= 0 && k < j) dp[j][i] = Math.max(dp[k][j] + 1 , 3 ); res = Math.max(res, dp[j][i]); } } return res; } }
分割回文串 II
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 class Solution { public int minCut (String s) { int n = s.length(); boolean [][] dp = new boolean [n][n]; for (int i = n - 1 ; i >= 0 ; --i) for (int j = i; j < n; ++j) if (s.charAt(i) == s.charAt(j) && (j - i <= 1 || dp[i + 1 ][j - 1 ])) dp[i][j] = true ; int [] memo = new int [n]; for (int i = 0 ; i < n; ++i) { if (dp[0 ][i]) memo[i] = 0 ; else { memo[i] = i; for (int j = 1 ; j <= i; ++j) { if (dp[j][i]) memo[i] = Math.min(memo[i], memo[j - 1 ] + 1 ); } } } return memo[n - 1 ]; } }
剑指 Offer II 096. 字符串交织
初始化出错了,debug半天
1 2 3 4 for (int i = 1 ; i <= n && s2.charAt(i - 1 ) == s3.charAt(i - 1 ); ++i) dp[0 ][i] = true ;for (int i = 1 ; i <= m && s1.charAt(i - 1 ) == s3.charAt(i - 1 ); ++i) dp[i][0 ] = true ;
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public boolean isInterleave (String s1, String s2, String s3) { int m = s1.length(), n = s2.length(), t = s3.length(); if (t != m + n) return false ; boolean [][] dp = new boolean [m + 1 ][n + 1 ]; dp[0 ][0 ] = true ; for (int i = 1 ; i <= n && s2.charAt(i - 1 ) == s3.charAt(i - 1 ); ++i) dp[0 ][i] = true ; for (int i = 1 ; i <= m && s1.charAt(i - 1 ) == s3.charAt(i - 1 ); ++i) dp[i][0 ] = true ; for (int i = 1 ; i <= m; ++i) for (int j = 1 ; j <= n; ++j) if ((s1.charAt(i - 1 ) == s3.charAt(i + j - 1 ) && dp[i - 1 ][j]) || (s2.charAt(j - 1 ) == s3.charAt(i + j - 1 ) && dp[i][j - 1 ])) dp[i][j] = true ; return dp[m][n]; } }
二刷记忆化搜索
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Solution { public int numDistinct (String s, String t) { m = s.length(); n = t.length(); this .s = s; this .t = t; dp = new int [m][n]; for (int [] arr : dp) Arrays.fill(arr, -1 ); return dfs(0 , 0 ); } public int dfs (int i, int j) { if (j == n) return 1 ; if (i == m) return 0 ; if (dp[i][j] != -1 ) return dp[i][j]; if (s.charAt(i) == t.charAt(j)) return dp[i][j] = dfs(i + 1 , j + 1 ) + dfs(i + 1 , j); return dp[i][j] = dfs(i + 1 , j); } int m, n; String s, t; int [][] dp; }
方法三:滚动数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public int numDistinct (String s, String t) { int m = s.length(), n = t.length(); int [] dp = new int [n + 1 ]; dp[0 ] = 1 ; for (int i = 1 ; i <= m; ++i) { for (int j = n; j >= 1 ; --j) { if (s.charAt(i - 1 ) == t.charAt(j - 1 )) dp[j] += dp[j - 1 ]; } } return dp[n]; } }
方法一:DP
注意初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution { public int numDistinct (String s, String t) { int m = s.length(), n = t.length(); if (m < n) return 0 ; int [][] dp = new int [m + 1 ][n + 1 ]; dp[0 ][0 ] = 1 ; for (int i = 1 ;i <= m; ++i) dp[i][0 ] = 1 ; for (int i = 1 ; i <= m; ++i) { for (int j = 1 ; j <= n; ++j) { if (s.charAt(i - 1 ) == t.charAt(j - 1 )) dp[i][j] = dp[i - 1 ][j - 1 ] + dp[i - 1 ][j]; else dp[i][j] = dp[i - 1 ][j]; } } return dp[m][n]; } }
方法二:记忆化搜索
优质题解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public int numDistinct (String s, String t) { m = s.length(); n = t.length(); dp = new int [m][n]; for (int [] arr : dp) Arrays.fill(arr, -1 ); return dfs(s, t, m - 1 , n - 1 ); } int m, n; int [][] dp; private int dfs (String s, String t, int i, int j) { if (j < 0 ) return 1 ; if (i < 0 ) return 0 ; if (dp[i][j] != -1 ) return dp[i][j]; if (s.charAt(i) == t.charAt(j)) return dp[i][j] = dfs(s, t, i - 1 , j - 1 ) + dfs(s, t, i - 1 , j); else return dp[i][j] = dfs(s, t, i - 1 , j); } }
方法一:DP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public boolean isSubsequence (String s, String t) { int m = s.length(), n = t.length(); boolean [][] dp = new boolean [m + 1 ][n + 1 ]; for (int j = 0 ; j <= n; ++j) dp[0 ][j] = true ; for (int i = 1 ; i <= m; ++i) for (int j = 1 ; j <= n; ++j) dp[i][j] = dp[i][j - 1 ] || dp[i - 1 ][j - 1 ] && s.charAt(i - 1 ) == t.charAt(j - 1 ); return dp[m][n]; } }
方法一:线性DP
相似题目 - 2008. 出租车的最大盈利 (和本题几乎一样)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public int maximizeTheProfit (int n, List<List<Integer>> offers) { List<int []>[] list = new List [n]; Arrays.setAll(list, e -> new ArrayList <>()); for (List<Integer> offer : offers) { int start = offer.get(0 ), end = offer.get(1 ), val = offer.get(2 ); list[end].add(new int []{start, val}); } int [] dp = new int [n + 1 ]; for (int i = 1 ; i <= n; ++i) { dp[i] = dp[i - 1 ]; for (int [] arr : list[i - 1 ]) { int start = arr[0 ], val = arr[1 ]; dp[i] = Math.max(dp[i], dp[start] + val); } } return dp[n]; } }
方法二:二分 + DP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public int maximizeTheProfit (int n, List<List<Integer>> offers) { Collections.sort(offers, (o1, o2) -> o1.get(1 ) - o2.get(1 )); int [] dp = new int [offers.size() + 1 ]; for (int i = 0 ; i < offers.size(); ++i) { dp[i + 1 ] = dp[i]; int j = bisearch(offers, i, offers.get(i).get(0 )); dp[i + 1 ] = Math.max(dp[i + 1 ], (j >= -1 ? dp[j + 1 ] : 0 ) + offers.get(i).get(2 )); } return dp[offers.size()]; } private int bisearch (List<List<Integer>> offers, int r, int start) { int l = 0 ; while (l <= r) { int mid = (l + r) >> 1 ; if (offers.get(mid).get(1 ) < start) l = mid + 1 ; else r = mid - 1 ; } return r; } }
方法一:线性DP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution { public long maxTaxiEarnings (int n, int [][] rides) { List<int []>[] list = new List [n + 1 ]; Arrays.setAll(list, o -> new ArrayList <>()); for (int [] ride : rides) { int from = ride[0 ], to = ride[1 ], tips = ride[2 ]; list[to].add(new int []{from, to - from + tips}); } long [] dp = new long [n + 1 ]; for (int i = 1 ; i <= n; ++i) { dp[i] = dp[i - 1 ]; for (int [] arr : list[i]) { int start = arr[0 ], val = arr[1 ]; dp[i] = Math.max(dp[i], dp[start] + val); } } return dp[n]; } }
数据量\(10^9\) ,如果用上述方法存储,会爆内存
方法一:二分 + DP
将jobs按照结束时间排序,dp[i]:处理前i分工作(做或者不做)能获得的最大利润,dp[0]没有意义(防止溢出)
对于第i份兼职,可以不做dp[i] = dp[i - 1],或者做dp[i] = dp[j + 1] + prof,其中j是满足jobs[j][1] <= jobs[i][0]的最大下标(dp数组是非递减的),可以使用二分来找到j
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Solution { public int jobScheduling (int [] startTime, int [] endTime, int [] profit) { int n = startTime.length; int [][] jobs = new int [n][3 ]; for (int i = 0 ; i < n; ++i) { jobs[i][0 ] = startTime[i]; jobs[i][1 ] = endTime[i]; jobs[i][2 ] = profit[i]; } Arrays.sort(jobs, (o1, o2) -> o1[1 ] - o2[1 ]); int [] dp = new int [n + 1 ]; for (int i = 1 ; i <= n; ++i) { dp[i] = dp[i - 1 ]; int start = jobs[i - 1 ][0 ], end = jobs[i - 1 ][1 ], p = jobs[i - 1 ][2 ]; int j = bisearch(jobs, i, start); dp[i] = Math.max(dp[i], dp[j + 1 ] + p); } return dp[n]; } private int bisearch (int [][] jobs, int r, int start) { int l = 0 ; while (l <= r) { int mid = (l + r) >> 1 ; if (jobs[mid][1 ] <= start) l = mid + 1 ; else r = mid - 1 ; } return r; } }
数量限制
方法一:二分 + 二维DP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public int maxValue (int [][] events, int k) { Arrays.sort(events, (o1, o2) -> o1[1 ] - o2[1 ]); int n = events.length; int [][] dp = new int [n + 1 ][k + 1 ]; for (int i = 0 ; i < n; ++i) { int p = bisearch(events, i, events[i][0 ]); for (int j = 1 ; j <= k; ++j) { dp[i + 1 ][j] = Math.max(dp[i][j], dp[p + 1 ][j - 1 ] + events[i][2 ]); } } return dp[n][k]; } private int bisearch (int [][] events, int r, int start) { int l = 0 ; while (l <= r) { int mid = (l + r) >> 1 ; if (events[mid][1 ] < start) l = mid + 1 ; else r = mid - 1 ; } return r; } }
方法一:二分查找 + 线性dp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public int maxTwoEvents (int [][] events) { int n = events.length; Arrays.sort(events, (o1, o2) -> o1[1 ] - o2[1 ]); int [][] dp = new int [n + 1 ][3 ]; for (int i = 0 ; i < n; ++i) { int p = bisearch(events, i, events[i][0 ]); for (int j = 1 ; j <= 2 ; ++j) { dp[i + 1 ][j] = Math.max(dp[i][j], dp[p + 1 ][j - 1 ] + events[i][2 ]); } } return dp[n][2 ]; } private int bisearch (int [][] events, int r, int start) { int l = 0 ; while (l <= r) { int mid = (l + r) >> 1 ; if (events[mid][1 ] < start) l = mid + 1 ; else r = mid - 1 ; } return r; } }
方法一:记忆化搜索
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { public int maxValue (int [][] grid) { this .grid = grid; m = grid.length; n = grid[0 ].length; dp = new int [m][n]; for (int [] arr : dp) Arrays.fill(arr, -1 ); return dfs(0 , 0 ); } private int dfs (int i, int j) { if (i == m && j == n) return 0 ; if (dp[i][j] != -1 ) return dp[i][j]; int res = grid[i][j], max = 0 ; for (int [] dir : dirs) { int row = i + dir[0 ], col = j + dir[1 ]; if (isValid(row, col)) { max = Math.max(max, dfs(row, col)); } } res += max; return dp[i][j] = res; } private boolean isValid (int i, int j) { return i < m && j < n; } int [][] grid, dirs = new int [][]{{1 , 0 }, {0 , 1 }}, dp; int m, n; }
方法二:简化写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class Solution { public int maxValue (int [][] grid) { this .grid = grid; m = grid.length; n = grid[0 ].length; dp = new int [m][n]; for (int [] arr : dp) Arrays.fill(arr, -1 ); return dfs(0 , 0 ); } private int dfs (int i, int j) { if (i >= m || j >= n) return 0 ; if (dp[i][j] != -1 ) return dp[i][j]; return dp[i][j] = Math.max(dfs(i + 1 , j), dfs(i, j + 1 )) + grid[i][j]; } private boolean isValid (int i, int j) { return i < m && j < n; } int [][] grid, dp; int m, n; }
方法三:DP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Solution { public int maxValue (int [][] grid) { this .grid = grid; m = grid.length; n = grid[0 ].length; dp = new int [m + 1 ][n + 1 ]; for (int i = 0 ; i < m; ++i) for (int j = 0 ; j < n; ++j) dp[i + 1 ][j + 1 ] = Math.max(dp[i][j + 1 ], dp[i + 1 ][j]) + grid[i][j]; return dp[m][n]; } int [][] grid, dp; int m, n; }
方法四:DP + 滚动数组
1 2 3 4 5 6 7 8 9 10 class Solution { public int maxValue (int [][] grid) { int m = grid.length, n = grid[0 ].length; int [] dp = new int [n + 1 ]; for (int i = 0 ; i < m; ++i) for (int j = 0 ; j < n; ++j) dp[j + 1 ] = grid[i][j] + Math.max(dp[j + 1 ], dp[j]); return dp[n]; } }
方法一:记忆化搜索
注意初始化dp数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Solution { public int minFallingPathSum (int [][] matrix) { this .matrix = matrix; n = matrix.length; int res = Integer.MAX_VALUE; dp = new int [n][n]; for (int [] arr : dp) Arrays.fill(arr, Integer.MAX_VALUE); for (int i = 0 ; i < n; ++i) { res = Math.min(res, dfs(0 , i)); } return res; } private int dfs (int i, int j) { if (i == n) return 0 ; if (dp[i][j] != Integer.MAX_VALUE) return dp[i][j]; int l = j > 0 ? dfs(i + 1 , j - 1 ) : Integer.MAX_VALUE; int d = dfs(i + 1 , j); int r = j < n - 1 ? dfs(i + 1 , j + 1 ) : Integer.MAX_VALUE; return dp[i][j] = Math.min(l, Math.min(d, r)) + matrix[i][j]; } int [][] matrix, dp; int n; }
方法二:简化写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { public int minFallingPathSum (int [][] matrix) { this .matrix = matrix; n = matrix.length; int res = Integer.MAX_VALUE; dp = new int [n][n]; for (int [] arr : dp) Arrays.fill(arr, Integer.MAX_VALUE); for (int i = 0 ; i < n; ++i) { res = Math.min(res, dfs(0 , i)); } return res; } private int dfs (int i, int j) { if (i == n) return 0 ; if (j < 0 || j == n) return Integer.MAX_VALUE; if (dp[i][j] != Integer.MAX_VALUE) return dp[i][j]; return dp[i][j] = Math.min(dfs(i + 1 , j - 1 ), Math.min(dfs(i + 1 , j), dfs(i + 1 , j + 1 ))) + matrix[i][j]; } int [][] matrix, dp; int n; }
方法三:DP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public int minFallingPathSum (int [][] matrix) { int n = matrix.length; int [][] dp = new int [n + 1 ][n + 2 ]; for (int i = 0 ; i <= n; ++i) { dp[i][0 ] = Integer.MAX_VALUE; dp[i][n + 1 ] = Integer.MAX_VALUE; } for (int i = 0 ; i < n; ++i) for (int j = 0 ; j < n; ++j) dp[i + 1 ][j + 1 ] = matrix[i][j] + Math.min(dp[i][j], Math.min(dp[i][j + 1 ], dp[i][j + 2 ])); int res = Integer.MAX_VALUE; for (int j = 1 ; j <= n; ++j) res = Math.min(res, dp[n][j]); return res; } }
方法四:滚动数组
用pre记录左上角的数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public int minFallingPathSum (int [][] matrix) { int n = matrix.length; int [] dp = new int [n + 2 ]; dp[0 ] = Integer.MAX_VALUE; dp[n + 1 ] = Integer.MAX_VALUE; for (int i = 0 ; i < n; ++i) dp[i + 1 ] = matrix[0 ][i]; for (int i = 1 ; i < n; ++i) { int pre = dp[0 ]; for (int j = 0 ; j < n; ++j) { int temp = pre; pre = dp[j + 1 ]; dp[j + 1 ] = matrix[i][j] + Math.min(temp, Math.min(pre, dp[j + 2 ])); } } int res = Integer.MAX_VALUE; for (int j = 1 ; j <= n; ++j) res = Math.min(res, dp[j]); return res; } }
方法一:记忆化搜索
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Solution { public int maxSumDivThree (int [] nums) { this .nums= nums; n = nums.length; dp = new int [n][3 ]; for (int [] arr : dp) Arrays.fill(arr, -1 ); return dfs(0 , 0 ); } private int dfs (int index, int sum) { if (index == n) return sum == 0 ? 0 : Integer.MIN_VALUE; if (dp[index][sum] != -1 ) return dp[index][sum]; return dp[index][sum] = Math.max(dfs(index + 1 , sum), dfs(index + 1 , (sum + nums[index]) % 3 ) + nums[index]); } int [] nums; int n; int [][] dp; }
1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution {public int maxSubarraySumCircular (int [] A) { int total = 0 , maxSum = A[0 ], curMax = 0 , minSum = A[0 ], curMin = 0 ; for (int a : A) { curMax = Math.max(curMax + a, a); maxSum = Math.max(maxSum, curMax); curMin = Math.min(curMin + a, a); minSum = Math.min(minSum, curMin); total += a; } return minSum != total ? Math.max(maxSum, total - minSum) : maxSum; } }
方法四:二刷
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { public int maximumSum (int [] arr) { nums = arr; n = arr.length; dp = new int [n][2 ][2 ]; for (int [][] a : dp) for (int [] b : a) Arrays.fill(b, -10001 ); return dfs(0 , 1 , false ); } private int dfs (int index, int key, boolean chosen) { if (index == n) return chosen ? 0 : Integer.MIN_VALUE; if (dp[index][key][chosen ? 1 : 0 ] != -10001 ) return dp[index][key][chosen ? 1 : 0 ]; int pass; if (chosen) pass = 0 ; else pass = dfs(index + 1 , key, false ); int choose = dfs(index + 1 , key, true ) + nums[index], delete = Integer.MIN_VALUE; if (key == 1 ) delete = dfs(index + 1 , 0 , chosen); int max = Math.max(Math.max(pass, choose), delete); res = Math.max(res, max); return dp[index][key][chosen ? 1 : 0 ] = max; } int [] nums; int n; int res = Integer.MIN_VALUE; int [][][] dp; }
方法一:记忆化搜索
由于至少要选一个元素,不能是空数组,所以复杂特别多
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 class Solution { public int maximumSum (int [] arr) { nums = arr; n = nums.length; dp = new int [n][2 ][2 ]; for (int [][] x : dp) for (int [] y : x) Arrays.fill(y, Integer.MIN_VALUE); return dfs(0 , 1 , false , false ); } private int dfs (int index, int key, boolean hasChosen, boolean stop) { if (stop) return 0 ; if (index == n) return hasChosen ? 0 : Integer.MIN_VALUE; if (dp[index][key][hasChosen ? 1 : 0 ] != Integer.MIN_VALUE) return dp[index][key][hasChosen ? 1 : 0 ]; int pass = Integer.MIN_VALUE, delete = Integer.MIN_VALUE, choose = Integer.MIN_VALUE; int chooseStop = Integer.MIN_VALUE, deleteStop = Integer.MIN_VALUE; if (!hasChosen) pass = dfs(index + 1 , key, hasChosen, false ); choose = dfs(index + 1 , key, true , false ) + nums[index]; chooseStop = dfs(index + 1 , key, true , true ) + nums[index]; if (hasChosen && key >= 1 ) { delete = dfs(index + 1 , 0 , true , false ); deleteStop = dfs(index + 1 , 0 , true , true ); } int temp = Math.max(pass, Math.max(choose, delete)); temp = Math.max(temp, Math.max(chooseStop, deleteStop)); res = Math.max(res, temp); return dp[index][key][hasChosen ? 1 : 0 ] = temp; } int [][][] dp; int [] nums; int n, res = Integer.MIN_VALUE; }
方法二:记忆化搜索
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Solution { public int maximumSum (int [] arr) { nums = arr; n = arr.length; dp = new int [n][2 ]; for (int [] x : dp) Arrays.fill(x, Integer.MIN_VALUE); int res = Integer.MIN_VALUE; for (int i = 0 ; i < n; ++i) res = Math.max(res, Math.max(dfs(i, 0 ), dfs(i, 1 ))); return res; } private int dfs (int i, int j) { if (i == n) return Integer.MIN_VALUE >> 1 ; if (dp[i][j] != Integer.MIN_VALUE) return dp[i][j]; if (j == 0 ) return dp[i][j] = Math.max(dfs(i + 1 , 0 ), 0 ) + nums[i]; return dp[i][j] = Math.max(dfs(i + 1 , 1 ) + nums[i], dfs(i + 1 , 0 )); } int [][] dp; int [] nums; int n, res = Integer.MIN_VALUE; }
方法三:1:1转换DP
1 2 3 4 5 6 7 8 9 10 11 12 13 class Solution { public int maximumSum (int [] nums) { int n = nums.length, res = Integer.MIN_VALUE; int [][] dp = new int [n + 1 ][2 ]; Arrays.fill(dp[0 ], Integer.MIN_VALUE >> 1 ); for (int i = 0 ; i < n; ++i) { dp[i + 1 ][0 ] = Math.max(dp[i][0 ], 0 ) + nums[i]; dp[i + 1 ][1 ] = Math.max(dp[i][1 ] + nums[i], dp[i][0 ]); res = Math.max(res, Math.max(dp[i + 1 ][0 ], dp[i + 1 ][1 ])); } return res; } }