# 动态规划

  • 一般是求最值的问题

  • 重叠子问题、最优子结构、状态转移方程就是动态规划三要素

  • 动规是由前一个状态推导出来的,而贪心是局部直接选最优的

一般思路:

  • 确定 dp 数组(dp table)以及下标的含义
  • 确定递推公式
  • dp 数组如何初始化
  • 确定遍历顺序
  • 举例推导 dp 数组

# 动态规划的核心原理

定义 dp 数组/函数的含义 -> 明确 base case -> 明确「状态」-> 明确「选择」

# 322.零钱兑换 (opens new window)

/**
 * @param {number[]} coins
 * @param {number} amount
 * @return {number}
 */
var coinChange = function (coins, amount) {
  // 初始化dp  dp[i]   i块钱需要dp[i]个最少硬币
  const dp = new Array(amount + 1).fill(amount + 1)
  dp[0] = 0
  for (let i = 0; i < dp.length; i++) {
    for (let coin of coins) {
      if (i - coin < 0) {
        continue
      }
      // 计算 min(dp[11-1],dp[11-2],dp[11-5])  +1
      dp[i] = Math.min(dp[i], dp[i - coin] + 1)
    }
  }
  return dp[amount] === amount + 1 ? -1 : dp[amount]
}

# 300. 最长递增子序列 (opens new window)

/**
 * @param {number[]} nums
 * @return {number}
 */
var lengthOfLIS = function (nums) {
  const n = nums.length
  const dp = new Array(n).fill(1)

  for (let i = 0; i < n; i++) {
    // nums[i] DP[i]
    // 遍历找到比j小的值
    for (let j = 0; j < i; j++) {
      if (nums[i] > nums[j]) {
        // dp[3] dp[j] +1 和dp[3] 的最大值
        dp[i] = Math.max(dp[j] + 1, dp[i])
      }
    }
  }
  let res = 0

  for (let i = 0; i < dp.length; i++) {
    res = Math.max(res, dp[i])
  }
  return res
}

# 198.打家劫舍 (opens new window)

/**
 * @param {number[]} nums
 * @return {number}
 */
var rob = function (nums) {
  // 初始化dp dp[i] 第i房间的时候 抢到的最高物品总价值
  const dp = new Array(nums.length)

  dp[0] = nums[0]
  dp[1] = Math.max(nums[1], nums[0])

  for (let i = 2; i < nums.length; i++) {
    // 怎么会达到当前房间的物品价值最高 抢前一个这个房间不强 或者这个上一个房间不强 抢上上个房间和这个房间
    dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i])
  }
  return dp[nums.length - 1]
}

# 买卖股票的最佳时机 (opens new window)

/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function (prices) {
  // dp[i][0] 第i天不持有股票的时候最大利润
  // dp[i][1] 第i天持有股票最大利润
  const dp = new Array(prices.length).fill().map((_) => [])
  dp[0][0] = 0
  dp[0][1] = -prices[0]
  for (let i = 1; i < prices.length; i++) {
    // 达到第i天不持有股票的状态
    // 昨天不持股,今天什么都不做;
    // 昨天持股,今天卖出股票(现金数增加)
    dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i])
    // 如果第i天持有股票的状态
    // 昨天持股,今天什么都不做(现金数与昨天一样);
    // 昨天不持股,今天买入股票(注意:只允许交易一次,因此手上的现金数就是当天的股价的相反数)
    dp[i][1] = Math.max(dp[i - 1][1], -prices[i])
  }
  return dp[prices.length - 1][0]
}

# 122. 买卖股票的最佳时机 II (opens new window)

/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function (prices) {
  // dp[i][0] 第i天不持有股票的时候现金 dp[i][1] 第i天持有股票最大现金
  const dp = new Array(prices.length).fill().map((_) => [])
  dp[0][0] = 0
  dp[0][1] = -prices[0]
  for (let i = 1; i < prices.length; i++) {
    // 达到第i天不持有股票的状态
    // 昨天不持股,今天什么都不做;
    // 昨天持股,今天卖出股票(现金数增加)
    dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i])
    // 如果第i天持有股票的状态
    // 昨天持股,今天什么都不做(现金数与昨天一样);
    // 昨天不持股,今天买入股票
    dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i])
  }
  return dp[prices.length - 1][0]
}