算法| ss 动态规划dp

  • 221.最大正方形
  • 1143.最长公共子序列—1
  • 300.最长递增子序列
  • 70.爬楼梯
  • 63.不同路径2
  • 64.最小路径和
  • 542.01 矩阵
  • 322.零钱兑换
  • 5.最长回文子串
  • 198.打家劫舍
  • 300.最长递增子序列
  • 494.目标和
  • 股票系列

221.最大正方形

/**
 * @param {character[][]} matrix
 * @return {number}
 */
// 思路
// dp初始化
// dp[i][j] 含义: 左 上  左上取 最小值  最后再加1
var maximalSquare = function (matrix) {
  const m = matrix.length;
  const n = matrix[0].length;
  const dp = Array(m)
    .fill(0)
    .map(() => Array(n).fill(0));

  let maxLen = 0;
  for (let i = 0; i < m; i++) {
    for (let j = 0; j < n; j++) {
      if (matrix[i][j] === "1") {
        if (i === 0 || j == 0) {
          dp[i][j] = 1;
        } else {
          dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1;
        }
        maxLen = Math.max(maxLen, dp[i][j]);
      }
    }
  }
  //   console.log(maxLen * maxLen);
  return maxLen * maxLen;
};

maximalSquare([
  ["1", "0", "1", "0", "0"],
  ["1", "0", "1", "1", "1"],
  ["1", "1", "1", "1", "1"],
  ["1", "0", "0", "1", "0"],
]);

1143.最长公共子序列—1

/**
 * @param {string} text1
 * @param {string} text2
 * @return {number}
 */
// 思路
// 构建dp 好像字符串比较是否相同 都是m+1 n+1 ,多一个,
// 双for循环时 s1取在第一个for之后取值 s2在第二个for里取值 ,都取前一个i-1 或者j-1
// dp[i][j]取值:相等时取左上角  不相等时取左或者上的最大值
// 返回结果
var longestCommonSubsequence = function (text1, text2) {
  const m = text1.length;
  const n = text2.length;

  const dp = Array(m + 1)
    .fill(0)
    .map(() => Array(n + 1).fill(0));

  for (let i = 1; i <= m; i++) {
    const s1 = text1[i - 1];
    for (let j = 1; j <= n; j++) {
      const s2 = text2[j - 1];
      if (s1 === s2) {
        dp[i][j] = dp[i - 1][j - 1] + 1;
      } else {
        dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
      }
    }
  }
  //   console.table(dp);
  return dp[m][n];
};

longestCommonSubsequence("abcde", "ace");
// ┌─────────┬───┬───┬───┬───┐
// │ (index) │ 0 │ 1 │ 2 │ 3 │
// ├─────────┼───┼───┼───┼───┤
// │    0    │ 0 │ 0 │ 0 │ 0 │
// │    1    │ 0 │ 1 │ 1 │ 1 │
// │    2    │ 0 │ 1 │ 1 │ 1 │
// │    3    │ 0 │ 1 │ 2 │ 2 │
// │    4    │ 0 │ 1 │ 2 │ 2 │
// │    5    │ 0 │ 1 │ 2 │ 3 │
// └─────────┴───┴───┴───┴───┘

300.最长递增子序列

/**
 * @param {number[]} nums
 * @return {number}
 */
var lengthOfLIS = function (nums) {
  const len = nums.length;
  //   初始值要为1
  let ans = 1;
  //   dp[0] 表示以 nums[0] 结尾的最长递增序列的长度
  const dp = Array(len).fill(1);
  for (let i = 1; i < len; i++) {
    for (let j = 0; j < i; j++) {
      if (nums[i] > nums[j]) {
        dp[i] = Math.max(dp[i], dp[j] + 1);
      }
    }
    console.log(dp);
    // 3. 更新结果
    ans = Math.max(ans, dp[i]);
  }
  return ans;
};

lengthOfLIS([10, 9, 2, 5, 3, 7, 101, 18]);

70.爬楼梯

/**
 * @param {number} n
 * @return {number}
 */
// 思路
// dp思路
// dp[i] 为跳n层台阶的方式
// 初始化 0 1 都只有1种方式
// 递推方式: 前面2种方式的和
var climbStairs = function (n) {
  const dp = [];
  dp[0] = 1;
  dp[1] = 1;
  for (let i = 2; i <= n; i++) {
    dp[i] = dp[i - 1] + dp[i - 2];
  }

  console.log(dp[n]);
  return dp[n];
};
climbStairs(2);
climbStairs(10);

63.不同路径2

/**
 * @param {number[][]} obstacleGrid
 * @return {number}
 */
// 思路
// dp三部曲
// 遇到障碍物怎么办  dp[i][j] = obstacleGrid[i][j] === 1 ? 0 : dp[i - 1][j] + dp[i][j - 1];
var uniquePathsWithObstacles = function (obstacleGrid) {
  // 先得到m,n
  const m = obstacleGrid.length;
  const n = obstacleGrid[0].length;
  // 1. 确定dp数组及下标
  dp = Array(m)
    .fill()
    .map((item) => Array(n).fill(0));
  //   2. dp递推公式
  // dp[i][j] = dp[i-1][j]+ dp[i][j-1]
  //   3. 初始化
  for (let i = 0; i < m && obstacleGrid[i][0] === 0; i++) {
    dp[i][0] = 1;
  }
  for (let j = 0; j < n && obstacleGrid[0][j] === 0; j++) {
    dp[0][j] = 1;
  }
  //   4. 遍历
  for (let i = 1; i < m; i++) {
    for (let j = 1; j < n; j++) {
      dp[i][j] = obstacleGrid[i][j] === 1 ? 0 : dp[i - 1][j] + dp[i][j - 1];
    }
  }
  return dp[m - 1][n - 1];
};

64.最小路径和

/**
 * @param {number[][]} grid
 * @return {number}
 */
// 思路
// dp三部曲完成
// dp[j][j] 的含义是到达ij这个位置的最小和
var minPathSum = function (grid) {
  const m = grid.length;
  const n = grid[0].length;
  //   1. 确定dp数组及下标
  dp = Array(m)
    .fill()
    .map((item) => Array(n).fill(0));
  //  2. 确定递推公式
  //   dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1]) + grid[i][j]
  // 3. 初始化
  dp[0][0] = grid[0][0];
  for (let i = 1; i < m; i++) {
    dp[i][0] = grid[i][0] + dp[i - 1][0];
  }
  for (let j = 1; j < n; j++) {
    dp[0][j] = grid[0][j] + dp[0][j - 1];
  }
  //   4. 遍历
  for (let i = 1; i < m; i++) {
    for (let j = 1; j < n; j++) {
      dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
    }
  }
  return dp[m - 1][n - 1];
};

542.01 矩阵

/**
 * @param {number[][]} mat
 * @return {number[][]}
 */
// 思路
// dp三部曲 + 双向遍历
// 初始化4条变
// 正向(左上)双for遍历 获取一次dp的值, 反向(右下)双for遍历 更新dp的值
// 不能同时4个方向,因为没有遍历到的时候 该值是Infinity
// 递推公式: 边界注意条件 分上下左右 判断取 最小值
var updateMatrix = function (mat) {
  const m = mat.length;
  const n = mat[0].length;
  const dp = Array(m)
    .fill(0)
    .map(() => Array(n).fill(Infinity));
  // 初始化4条边
  for (let i = 0; i < m; i++) {
    if (mat[i][0] === 0) dp[i][0] = 0;
    if (mat[i][n - 1] === 0) dp[i][n - 1] = 0;
  }
  for (let j = 0; j < n; j++) {
    if (mat[0][j] === 0) dp[0][j] = 0;
    if (mat[m - 1][j] === 0) dp[m - 1][j] = 0;
  }

  for (let i = 0; i < m; i++) {
    for (let j = 0; j < n; j++) {
      if (mat[i][j] === 0) {
        dp[i][j] = 0;
      } else {
        // 上
        if (i - 1 >= 0) dp[i][j] = Math.min(dp[i][j], dp[i - 1][j] + 1);
        // 左
        if (j - 1 >= 0) dp[i][j] = Math.min(dp[i][j], dp[i][j - 1] + 1);
      }
    }
  }
  //   console.table(dp);
  for (let i = m - 1; i >= 0; i--) {
    for (let j = n - 1; j >= 0; j--) {
      // 下
      if (i + 1 < m) dp[i][j] = Math.min(dp[i][j], dp[i + 1][j] + 1);
      // 右
      if (j + 1 < n) dp[i][j] = Math.min(dp[i][j], dp[i][j + 1] + 1);
    }
  }
  console.table(dp);
  return dp;
};
// updateMatrix([
//   [0, 0, 0],
//   [0, 1, 0],
//   [0, 0, 0],
// ]);
updateMatrix([
  [0, 0, 0],
  [0, 1, 0],
  [1, 1, 1],
]);
updateMatrix([
  [0, 1, 0, 1, 1],
  [1, 1, 0, 0, 1],
  [0, 0, 0, 1, 0],
  [1, 0, 1, 1, 1],
  [1, 0, 0, 0, 1],
]);
// updateMatrix([
//   [0, 1, 0, 1, 1],
//   [1, 1, 0, 0, 1],
//   [0, 0, 0, 1, 0],
//   [1, 0, 1, 1, 1],
//   [1, 0, 0, 0, 1],
// ]);
// 输入:mat = [[0,0,0],[0,1,0],[0,0,0]]
// 输出:[[0,0,0],[0,1,0],[0,0,0]]

// 输入:mat = [[0,0,0],[0,1,0],[1,1,1]]
// 输出:[[0,0,0],[0,1,0],[1,2,1]]
// ┌─────────┬───┬───┬───┐
// │ (index) │ 0 │ 1 │ 2 │
// ├─────────┼───┼───┼───┤
// │    0    │ 0 │ 0 │ 0 │
// │    1    │ 0 │ 1 │ 0 │
// │    2    │ 1 │ 2 │ 1 │
// └─────────┴───┴───┴───┘

322.零钱兑换

/**
 * @param {number[]} coins
 * @param {number} amount
 * @return {number}
 */
// 思路
// 理解dp[i][j]的含义
// 这题和背包有点像,需要多学习
var coinChange = function (coins, amount) {
  let n = coins.length;
  //   二维数组:状态定义:dp[i][j]表示从前 i 个物品中选择不超过 j 重量的物品的最大价值
  const dp = Array(n + 1)
    .fill(0)
    .map(() => Array(amount + 1).fill(amount + 1));
  dp[0][0] = 0;
  for (let i = 1; i < n + 1; i++) {
    for (let j = 0; j < amount + 1; j++) {
      // 背包容量小于当前物品的容量,即背包容量已经不足以拿第 i 个物品了
      // 第 i 个物品即下标为 i - 1 的那个物品
      if (j < coins[i - 1]) {
        dp[i][j] = dp[i - 1][j];
      } else {
        // 背包容量足够拿第 i 个物品,那么可拿也可不拿
        // 1、拿了,那么最大价值是前 i 个物品扣除第 i 个物品的重量的情况下的最大价值,再加上【第 i 个】物品的价值
        // 2、不拿,那么就是从前 i - 1 个物品中选择出的最大价值
        dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - coins[i - 1]] + 1);
      }
    }
  }
  console.log(dp[n][amount] == amount + 1 ? -1 : dp[n][amount]);
  return dp[n][amount] == amount + 1 ? -1 : dp[n][amount];
};
// coinChange([1, 2, 5], 11);
coinChange([2], 3);
// 输入:coins = [1, 2, 5], amount = 11
// 输出:3
// 解释:11 = 5 + 5 + 1

5.最长回文子串

/**
 * @param {string} s
 * @return {string}
 */
// 思路
// dp[i][j] 定义 i~j之间的字符串是不是回文
// 从尾部进行截取字符串
var longestPalindrome = function (s) {
  let len = s.length;
  let res = "";
  let dp = Array(len)
    .fill(0)
    .map(() => Array(len).fill(false));
  for (let i = len - 1; i >= 0; i--) {
    for (let j = i; j < len; j++) {
      //dp[i][j]表示子串i~j是否是回文子串
      //回文子串必须满足s[i],s[j]相等。并且向外扩展一个字符也相等,即dp[i+1][j-1]也是回文子串
      //j - i < 2表示子串小于等于1也是回文串
      dp[i][j] = s[i] === s[j] && (j - i < 2 || dp[i + 1][j - 1]);
      if (dp[i][j] && j - i + 1 > res.length) {
        // substring 不包含尾部j+1
        res = s.substring(i, j + 1);
      }
    }
  }
  console.log(res);
  return res;
};
// "babad"
longestPalindrome("babad");
// d
// d
// a
// ad
// b
// bad
// a
// abad

// b
// babad

198.打家劫舍

/**
 * @param {number[]} nums
 * @return {number}
 */
// 思路
// dp三部曲
var rob = function (nums) {
  // dp[i] 含义: 第i个房间的最大值
  // 先初始化 1 2个房间的最大值, 第一个就是它自己, 第二个取第一个 第二个的最大值
  // 递推公式:dp[i] = Math.max(dp[i-2]+dp[i] , dp[i-1]) 往前2个的dp值 + 当前房间的值和前1个的值的最大值
  const dp = [nums[0], Math.max(nums[0], nums[1])];
  // dp遍历 从第二个开始
  for (let i = 2; i < nums.length; i++) {
    // 记住前一个dp值加上当前的数组值,而非dp[i]
    dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);
  }
  return dp[nums.length - 1];
};
console.log(rob([1, 2, 3, 1]));
// 输入:[1,2,3,1]
// 输出:4

300.最长递增子序列

/**
 * @param {number[]} nums
 * @return {number}
 */
var lengthOfLIS = function (nums) {
  const len = nums.length;
  //   初始值要为1
  let ans = 1;
  //   dp[0] 表示以 nums[0] 结尾的最长递增序列的长度
  const dp = Array(len).fill(1);
  for (let i = 1; i < len; i++) {
    //  j<i
    for (let j = 0; j < i; j++) {
      if (nums[i] > nums[j]) {
        dp[i] = Math.max(dp[i], dp[j] + 1);
      }
    }
    console.log(dp);
    // 3. 更新结果
    ans = Math.max(ans, dp[i]);
  }
  return ans;
};

lengthOfLIS([10, 9, 2, 5, 3, 7, 101, 18]);

494.目标和

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
// 思路
// 将nums分成2部分: left right
// 计算转变: left+right= sum  left-right = target ==> left = (sum+target) / 2
// 左边集合的总值确定了left, 题意转换成把nums数据塞满左背包有哪些方式
var findTargetSumWays = function (nums, target) {
  const sum = nums.reduce((x, y) => x + y);
  if (Math.abs(target) > sum) return 0;
  if ((target + sum) % 2) return 0;
  const left = (target + sum) / 2;
  const dp = Array(left + 1).fill(0);
  //   左背包空的时候也是一种方案
  dp[0] = 1;
  //   遍历物品
  for (let i = 0; i < nums.length; i++) {
    // 遍历背包
    console.log(i);
    for (let j = left; j >= nums[i]; j--) {
      // 求装满背包有几种方法的情况下,递推公式一般为:dp[j] += dp[j-nums[i]]。
      dp[j] += dp[j - nums[i]];
      console.log(dp);
    }
  }
  return dp[left];
};
findTargetSumWays([1, 1, 1, 1, 1], 3);
// nums = [1,1,1,1,1], target = 3

股票系列

// 188. 买卖股票的最佳时机 IV

// 给你一个整数数组 prices 和一个整数 k ,其中 prices[i] 是某支给定的股票在第 i 天的价格
/**
 * @param {number} k
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function (k, prices) {
  const len = prices.length;
  // 初始化 dp定义 dp[i][j][0] 第i天k次交易不持有股票的利润 dp[i][j][1] 第i天k次交易持有股票的利润
  const dp = Array(len)
    .fill(0)
    .map(() => Array(k + 1).fill([0, 0]));
  for (let j = 1; j < k + 1; j++) {
    dp[0][j][1] = -prices[0];
  }
  console.log(dp);
  for (let i = 1; i < len; i++) {
    for (let j = 1; j < k + 1; j++) {
      // 不持有股票,全卖 = 昨天就没股票了, 或者昨天有今天卖掉
      dp[i][j][0] = Math.max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i]);
      //   买了一只股票 = 昨天就有一只股票 或者 昨天没股票今天买了一只股票
      dp[i][j][1] = Math.max(dp[i - 1][j][1], dp[i - 1][j][0] - prices[i]);
    }
    console.table(dp);
  }
  return dp[len - 1][k][0];
};
console.log(maxProfit(2, [2, 4, 1]));
// 输入:k = 2, prices = [2,4,1]
// 输出:2

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值