这个系列感觉有点大了,而且也特别有套路的感觉
image.png

322. 零钱兑换

image.png

  1. var coinChange = function(coins, amount) {
  2. const dp = new Array(amount + 1).fill(Infinity); // 多一项
  3. dp[0] = 0; // 凑出0元,不需要硬币
  4. for (let i = 1; i <= amount; i++) { // 从 1 元开始凑
  5. for (let coin of coins) {
  6. if (i - coin < 0) { // 不能选这个硬币
  7. continue;
  8. }
  9. dp[i] = Math.min(dp[i], dp[i - coin] + 1); // 这样算最小的技巧
  10. }
  11. }
  12. return dp[amount] !== Infinity ? dp[amount] : -1;
  13. };


300. 最长递增子序列

image.png
缩小问题范围。

  1. var lengthOfLIS = function(nums) {
  2. let n = nums.length;
  3. if (n <= 1) {
  4. return n;
  5. }
  6. let dp = new Array(n).fill(1);
  7. for (let i = 1; i < n; i++) {
  8. // 前 i - 1 个数里,比 nums[i] 小的数对应的 dp 最大的数
  9. for (let j = 0; j <= i - 1; j++) {
  10. if (nums[j] < nums[i]) {
  11. dp[i] = Math.max(dp[i], dp[j]) + 1;
  12. }
  13. }
  14. }
  15. return Math.max(...dp);
  16. };

279. 完全平方数

image.png
状态转移方程的寻找,之前局限于 i 到 i - 1,通过这道题我们知道还可以跳跃: i 到 j ( 1 <= j <= i * i)

  1. var numSquares = function(n) {
  2. const dp = (new Array(n + 1)).fill(0); // 初始值边界应该多少?自己应该去举例
  3. for (let i = 1; i <= n; i++) { // 下标的取法,也要去试验
  4. let min = Infinity;
  5. for (let j = 1; j * j <= i; j++) { // dp 的上一步需要取最小才行
  6. if (min > dp[i - j * j]) {
  7. min = dp[i - j * j];
  8. }
  9. }
  10. dp[i] = min + 1;
  11. }
  12. return dp[n];
  13. };

96. 不同的二叉搜索树

  1. var numTrees = function(n) {
  2. let dp = new Array(n + 1).fill(0);
  3. dp[0] = 1;
  4. dp[1] = 1;
  5. for (let i = 2; i <= n; i++) {
  6. for (let j = 0; j < i; j++) {
  7. dp[i] += dp[j] * dp[i - 1 - j]; // 就是硬找规律
  8. }
  9. }
  10. return dp[n];
  11. };

矩阵 和 路径

64. 最小路径和

以相邻的块 决定 下一个 dp 的值

  1. var minPathSum = function(grid) {
  2. let rows = grid.length;
  3. let cols = grid[0].length;
  4. let dp = new Array(rows).fill(-1).map(() => new Array(rows).fill(-1)); // 题目要求 非负
  5. for (let row = rows - 1; row >= 0; row--) {
  6. for (let col = cols - 1; col >= 0; col--) {
  7. let bottom = (row + 1 < rows) ? dp[row + 1][col] : Infinity;
  8. let right = (col + 1 < cols) ? dp[row][col + 1] : Infinity;
  9. let min = Math.min(bottom, right);
  10. dp[row][col] = (min === Infinity ? 0 : min) + grid[row][col];
  11. }
  12. }
  13. return dp[0][0];
  14. };

但是,也有 dfs 思路:
岛屿问题中经常使用的思路。

  1. var minPathSum = function(grid) {
  2. let rows = grid.length;
  3. let cols = grid[0].length;
  4. let ans = [];
  5. function dfs(path, row, col) {
  6. if (row === rows - 1 && col === cols - 1) {
  7. let sum = path.reduce((pre, cur) => pre + cur, 0) + grid[row][col];
  8. ans.push(sum);
  9. return;
  10. }
  11. if (!(0 <= row && row < rows && 0 <= col && col < cols)) {
  12. return;
  13. }
  14. // 回溯的那感觉啊
  15. dfs([...path, grid[row][col]], row, col + 1);
  16. dfs([...path, grid[row][col]], row + 1, col);
  17. }
  18. dfs([], 0, 0);
  19. return Math.min(...ans);
  20. };

62. 不同路径

  1. var uniquePaths = function(m, n) {
  2. if (m === 1 || n === 1) {
  3. return 1;
  4. }
  5. let dp = new Array(m).fill(0).map(() => new Array(n).fill(0));
  6. for (let row = m - 1; row >= 0; row--) {
  7. for (let col = n - 1; col >= 0; col--) {
  8. if (row === (m - 1) || col === (n - 1)) {
  9. dp[row][col] = 1;
  10. continue;
  11. }
  12. dp[row][col] = dp[row + 1][col] + dp[row][col + 1];
  13. }
  14. }
  15. return dp[0][0];
  16. };

63. 不同路径 II

和 62 差不多的解法。

  1. var uniquePathsWithObstacles = function(obstacleGrid) {
  2. let m = obstacleGrid.length;
  3. let n = obstacleGrid[0].length;
  4. if (m === 1 || n === 1) {
  5. let tmp = obstacleGrid.flat();
  6. if (tmp.includes(1)) {
  7. return 0;
  8. }
  9. return 1;
  10. }
  11. let dp = new Array(m).fill(0).map(() => new Array(n).fill(0));
  12. for (let row = m - 1; row >= 0; row--) {
  13. for (let col = n - 1; col >= 0; col--) {
  14. if (obstacleGrid[row][col] === 1) {
  15. dp[row][col] = 0
  16. continue;
  17. }
  18. if ((row === m - 1) && (col === n - 1)) {
  19. dp[row][col] = 1;
  20. continue;
  21. }
  22. if (row + 1 >= m) {
  23. dp[row][col] = dp[row][col + 1];
  24. } else if (col + 1 >= n) {
  25. dp[row][col] = dp[row + 1][col];
  26. } else {
  27. dp[row][col] = dp[row + 1][col] + dp[row][col + 1];
  28. }
  29. }
  30. }
  31. return dp[0][0];
  32. };

45. 跳跃游戏 II

  1. var jump = function(nums) {
  2. let n = nums.length;
  3. let dp = new Array(n).fill(Infinity);
  4. dp[n - 1] = 0; // 边界条件写明白
  5. for (let i = n - 2; i >= 0; i--) {
  6. let cur = nums[i];
  7. if (cur === 0) {
  8. dp[i] = Infinity;
  9. } else {
  10. let min = Infinity;
  11. for (let j = 1; (j <= cur && j < n); j++) { // 递归算步伐
  12. if (dp[i + j] === Infinity) {
  13. continue;
  14. }
  15. if (min > dp[i + j]) { // 取最小
  16. min = dp[i + j];
  17. }
  18. }
  19. dp[i] = min + 1;
  20. }
  21. }
  22. return dp[0];
  23. };

32. 最长有效括号

做过最难的动态规划,但也是典型的动态规划,并不超纲。
image.png

  1. var longestValidParentheses = function(s) {
  2. let n = s.length;
  3. if (n <= 1) {
  4. return 0;
  5. }
  6. if (n === 2) {
  7. return s === '()' ? 2 : 0;
  8. }
  9. let dp = new Array(n).fill(0);
  10. for (let i = 0; i < n; i++) {
  11. let cur = s[i];
  12. if (cur === '(') {
  13. dp[i] = 0;
  14. } else {
  15. if (s[i - 1] === '(') {
  16. dp[i] = 2 + ((i - 2 >= 0) ? dp[i - 2] : 0);
  17. } else {
  18. if (i - dp[i - 1] - 1 >= 0 && s[i - dp[i - 1] - 1] === '(') {
  19. dp[i] = 2 + dp[i - 1] + ((i - dp[i - 1] - 2 >= 0) ? dp[i - dp[i - 1] - 2] : 0);
  20. }
  21. }
  22. }
  23. }
  24. return Math.max(...dp);
  25. };

221. 最大正方形

规律不是特别好理解:dp[i][j] 表示 以 matrix[i][j] 为 右下顶点 的正方形的最大边长。

  1. var maximalSquare = function(matrix) { // 这动态规划好厉害
  2. let rows = matrix.length;
  3. let cols = matrix[0].length;
  4. const dp = new Array(rows).fill(0).map(() => new Array(cols).fill(0));
  5. let max = 0;
  6. for (let row = 0; row < rows; row++) {
  7. for (let col = 0; col < cols; col++) {
  8. if (matrix[row][col] === '1') { // 更简洁的写法:只有为1才会被操作
  9. if (row === 0 || col === 0) { // 边上元素单独标记
  10. dp[row][col] = matrix[row][col] === '1' ? 1: 0;
  11. } else {
  12. dp[row][col] = Math.min(dp[row - 1][col - 1], dp[row - 1][col], dp[row][col - 1]) + 1;
  13. }
  14. if (max < dp[row][col]) {
  15. max = dp[row][col];
  16. }
  17. }
  18. }
  19. }
  20. return max * max;
  21. };

91. 解码方法

  1. var numDecodings = function(s) { // 动态规划,没有要求具体的例子,只是要数目
  2. let n = s.length;
  3. if (n === 0) {
  4. return 0;
  5. }
  6. let dp = new Array(n + 1).fill(0);
  7. dp[0] = 1;
  8. for (let i = 1; i < n + 1; i++) {
  9. if (s[i - 1] !== '0') {
  10. dp[i] = dp[i - 1];
  11. }
  12. let cur = s.slice(i - 2, i);
  13. if (Number(cur) <= 26 && (s[i - 2] !== '0') && i >= 2) {
  14. dp[i] += dp[i - 2]
  15. }
  16. }
  17. return dp[n];
  18. };

上台阶问题

62. 不同路径

  1. var uniquePaths = function(m, n) {
  2. if (m === 1 || n === 1) {
  3. return 1;
  4. }
  5. let dp = new Array(m).fill(0).map(() => new Array(n).fill(0));
  6. for (let row = m - 1; row >= 0; row--) {
  7. for (let col = n - 1; col >= 0; col--) {
  8. if (row === (m - 1) || col === (n - 1)) {
  9. dp[row][col] = 1;
  10. continue;
  11. }
  12. dp[row][col] = dp[row + 1][col] + dp[row][col + 1];
  13. }
  14. }
  15. return dp[0][0];
  16. };

63. 不同路径 II

稍微比62考虑情况多一点

  1. var uniquePathsWithObstacles = function(obstacleGrid) {
  2. let m = obstacleGrid.length;
  3. let n = obstacleGrid[0].length;
  4. if (m === 1 || n === 1) {
  5. let tmp = obstacleGrid.flat();
  6. if (tmp.includes(1)) {
  7. return 0;
  8. }
  9. return 1;
  10. }
  11. let dp = new Array(m).fill(0).map(() => new Array(n).fill(0));
  12. for (let row = m - 1; row >= 0; row--) {
  13. for (let col = n - 1; col >= 0; col--) {
  14. if (obstacleGrid[row][col] === 1) {
  15. dp[row][col] = 0
  16. continue;
  17. }
  18. if ((row === m - 1) && (col === n - 1)) {
  19. dp[row][col] = 1;
  20. continue;
  21. }
  22. if (row + 1 >= m) {
  23. dp[row][col] = dp[row][col + 1];
  24. } else if (col + 1 >= n) {
  25. dp[row][col] = dp[row + 1][col];
  26. } else {
  27. dp[row][col] = dp[row + 1][col] + dp[row][col + 1];
  28. }
  29. }
  30. }
  31. return dp[0][0];
  32. };

45. 跳跃游戏 II

  1. var jump = function(nums) {
  2. let n = nums.length;
  3. let dp = new Array(n).fill(Infinity);
  4. dp[n - 1] = 0; // 边界条件写明白
  5. for (let i = n - 2; i >= 0; i--) {
  6. let cur = nums[i];
  7. if (cur === 0) {
  8. dp[i] = Infinity;
  9. } else {
  10. let min = Infinity;
  11. for (let j = 1; (j <= cur && j < n); j++) { // 递归算步伐
  12. if (dp[i + j] === Infinity) {
  13. continue;
  14. }
  15. if (min > dp[i + j]) { // 取最小
  16. min = dp[i + j];
  17. }
  18. }
  19. dp[i] = min + 1;
  20. }
  21. }
  22. return dp[0];
  23. };