跟着卡哥学算法Day 41:动态规划part8
正在加载今日诗词....
2025-03-24

121. 买卖股票的最佳时机 🌟🌟

力扣链接 🌟🌟

题目描述

给定一个数组 prices ,它的第  i 个元素  prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

  • 示例 1:
  • 输入:[7,1,5,3,6,4]
  • 输出:5
    解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
  • 示例 2:
  • 输入:prices = [7,6,4,3,1]
  • 输出:0
    解释:在这种情况下, 没有交易完成, 所以最大利润为 0。

解题思路

暴力方法:两层 for 循环 贪心法:取最左侧最小价格,取右边最大值,计算最大利润

var maxProfit = function (prices) {
  let lowerPrice = prices[0]
  let profit = 0
  for (let i = 0; i < prices.length; i++) {
    lowerPrice = Math.min(lowerPrice, prices[i])
    profit = Math.max(profit, prices[i] - lowerPrice)
  }

  return profit
}

某些股票买卖具体场景下可以使用贪心或其他思路解决,但动态规划是解决这一系列的题目的通用方法

动规五部曲:

  1. 确定 dp 数组及下标的含义

    每天有两种状态:

    • 持有这只股股票所得的最多现金
    • 不持有这只股股票所得的最多现金

    因此 dp 数组必须是个二维数组表示

    • dp[i][0]:表示第 i 天持有股票所得最多现金
    • dp[i][1]:表示第 i 天不持有股票所得最多现金

    “持有”不代表当天买入,也有可能昨天已经买入,今天保持持有的状态

  2. 确定递推公式

    dp[i][0]即第 i 天持有股票可以由两个状态推出:

    1. dp[i-1][0] 即第 i-1 天已经持有该股票,今天保持现状,所得现金
    2. 第 i 天买入,所得现金就是今天买入股票后所得现金 0 - prices[i] = -prices[i],初始现金为 0

    dp[i][0]在两者中选最大的:

    `dp[i][0]` = Math.max(dp[i - 1][0], -prices[i])
    

    dp[i][1]即第 i 天不持有股票,也由两个状态推出来

    1. dp[i-1][1] 即第 i-1 天不持有股票,今天保持现状,所得现金
    2. 第 i 天卖出股票,所得现金为 dp[i-1][0] + prices[i]
    `dp[i][1]` = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i])
    
  3. dp 数组初始化

    从递推公式得出需要考虑 dp[i-1][0]dp[i-1][1],所以必须初始化 dp[0][0]和 dp[0][1]

    • dp[0][0] = -prices[0],第 0 天持有股票,即买入,所得现金为 -prices[0]
    • dp[0][1] = 0, 第 0 天不持有股票
  4. 确定遍历顺序

    从前往后

  5. 举例推导 dp 数组

    以 prices = [7,1,5,3,6,4] 为例,得到的 dp 数组为:

    dp = [
      [-7, 0],
      [-1, 0],
      [-1, 4],
      [-1, 4],
      [-1, 5],
      [-1, 5],
    ]
    

代码

var maxProfit = function (prices) {
  const n = prices.length

  const dp = new Array(n).fill().map(() => [0, 0])

  dp[0][0] = -prices[0]

  for (let i = 1; i < n; i++) {
    // 第i天持有
    dp[i][0] = Math.max(dp[i - 1][0], -prices[i])
    // 第i天不持有
    dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] + prices[i])
  }
  console.log(dp)

  return Math.max(...dp[n - 1])
}

122.买卖股票的最佳时机 II 🌟🌟

力扣链接 🌟🌟

题目描述

给定一个数组,它的第  i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

  • 示例 1:
  • 输入: [7,1,5,3,6,4]
  • 输出: 7
    解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4。随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
  • 示例 2:
  • 输入: [1,2,3,4,5]
  • 输出: 4
    解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
  • 示例  3:
  • 输入: [7,6,4,3,1]
  • 输出: 0
    解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

提示:

  • 1 <= prices.length <= 3 * 10 ^ 4
  • 0 <= prices[i] <= 10 ^ 4

解题思路

贪心算法:买卖股票的最佳时机讲过

prices[3] - prices[0]
= (prices[3] - prices[2]) + (prices[2] - prices[1]) + (prices[1] - prices[0])

所以只要今天比昨天高,就卖出 prices[i] - prices[i-1] > 0

动规五部曲:

  1. 确定 dp 数组及下标的含义

    • dp[i][0] 表示第 i 天持有股票所得最多现金
    • dp[i][1] 表示第 i 天不持有股票所得最多现金
  2. 确定递推公式

    股票可以买卖多次只能买卖一次的区别在于:第 i 天买入时,现金可能不是 0

    dp[i][0] 即第 i 天持有股票的最大金额,可以由两个状态推出:

    1. dp[i - 1][0] 即第 i-1 天持有股票的最大金额
    2. dp[i - 1][1] - prices[i] 即第 i 天买入股票,所得最的金额

    dp[i][0] 在两者中选最大的:

    dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] - prices[i])
    

    这里与买卖股票的最佳时机的区别是:

    • 第 i 天买入股票,所得现金为 -prices[i],即初始金额为 0,第一次买入
    • 此处第 i 天买入股票,所得现金为 dp[i - 1][1] - prices[i]初始金额为前一天不持有此股票的最大金额
  3. dp 数组初始化

    • dp[0][0] = -prices[0],第 0 天持有股票,即买入,所得现金为 -prices[0]
    • dp[0][1] = 0,第 0 天不持有股票
  4. 确定遍历顺序

    从前往后

  5. 举例推导 dp 数组

    以 prices = [7,1,5,3,6,4] 为例,得到的 dp 数组为:

    dp = [
      [-7, 0],
      [0, 0],
      [0, 4],
      [2, 4],
      [2, 7],
      [3, 7],
    ]
    

代码

var maxProfit = function (prices) {
  const n = prices.length
  const dp = new Array(n).fill().map(() => [0, 0])

  // 第0天持有股票
  dp[0][0] = -prices[0]
  dp[0][1] = 0

  for (let i = 1; i < n; 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])
  }
  console.log(dp)
  return Math.max(...dp[n - 1])
}

123.买卖股票的最佳时机 III 🌟🌟

力扣链接 🌟🌟

题目描述

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成   两笔   交易。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

  • 示例  1:
  • 输入:prices = [3,3,5,0,0,3,1,4]
  • 输出:6 解释:在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3。
  • 示例 2:
  • 输入:prices = [1,2,3,4,5]
  • 输出:4 解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4。注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
  • 示例 3:
  • 输入:prices = [7,6,4,3,1]
  • 输出:0 解释:在这个情况下, 没有交易完成, 所以最大利润为 0。
  • 示例 4:
  • 输入:prices = [1] 输出:0

提示:

  • 1 <= prices.length <= 10^5
  • 0 <= prices[i] <= 10^5

解题思路

至多买卖两次,意味着可以买卖一次,买卖两次,也可以不买卖

一天可以有五种状态:

  1. 没有操作
  2. 第一次持有股票
  3. 第一次不持有股票
  4. 第二次持有股票
  5. 第二次不持有股票

动规五部曲:

  1. 确定 dp 数组及下标的含义

    dp[i][j] 中 i 表示第 i 天,j 表示五种状态[0-4],所以 dp[i][j]表示第 i 天状态 j 下所剩最大金额

    如:dp[i][1] 表示第 i 天持有股票的状态,并不是第 i 天一定买入,有可能第 i-1 天就买入了

  2. 确定递归公式

    达到 dp[i][1] 状态有两种情况:

    1. 第 i 天买入股票,那么 dp[i][1] = dp[i-1][0] - prices[i]
    2. 第 i 天保持现状,沿用前一天买入股票的状态,即:dp[i][1] = dp[i-1][1]

    两者选择最大的:

    dp[i][1] = Math.max(dp[i - 1][0] - prices[i], dp[i - 1][1])
    

    同理,达到 dp[i][2] 状态有两种情况:

    1. 第 i 天卖出股票,那么 dp[i][2] = dp[1 - 1][1] + prices[i]
    2. 第 i 天保持现状,沿用前一天卖出股票的状态,即:dp[i][2] = dp[i-1][2]
    dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][1] + prices[i])
    

    同理,达到 dp[i][3]dp[i][4] 也分别有两种情况:

    1. 第 i 天买入/卖出
    2. 第 i 天保持现状,沿用前一天状态
    dp[i][3] = Math.max(dp[i - 1][3], dp[i - 1][2] - prices[i])
    dp[i][4] = Math.max(dp[i - 1][4], dp[i - 1][3] + prices[i])
    
  3. dp 数组初始化

    • dp[0][0] = 0,第 0 天不做操作,金额为 0
    • dp[0][1] = -prices[0],第 0 天第一次买入股票,金额为 -prices[0]
    • dp[0][2] = 0,第 0 天第一次卖出股票,即当天买入当天卖出,金额为 0
    • dp[0][3] = -prices[0],第 0 天第二次买入股票,即第一次买入又卖出又重新买入,金额为 -prices[0]
    • dp[0][4] = 0,第 0 天第二次卖出股票,即第一次买入又卖出又重新卖出,金额为 0
  4. 确定遍历顺序

    从前往后

  5. 举例推导 dp 数组

    以 prices = [1,2,3,4,5] 为例,得到的 dp 数组为:

    dp = [
      [0, -1, 0, -1, 0],
      [0, -1, 1, -1, 1],
      [0, -1, 2, -1, 2],
      [0, -1, 3, -1, 3],
      [0, -1, 4, -1, 4],
    ]
    

代码

var maxProfit = function (prices) {
  const n = prices.length
  const dp = new Array(n).fill().map(() => new Array(5).fill(0))

  dp[0][1] = -prices[0]
  dp[0][3] = -prices[0]

  // j
  // 1 第一次持有
  // 2 第一次卖出
  // 3 第二次持有
  // 4 第二次卖出
  for (let i = 1; i < n; i++) {
    const price = prices[i]
    dp[i][0] = dp[i - 1][0]
    dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - price)
    dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][1] + price)
    dp[i][3] = Math.max(dp[i - 1][3], dp[i - 1][2] - price)
    dp[i][4] = Math.max(dp[i - 1][4], dp[i - 1][3] + price)
  }
  console.log(dp)
  return dp[n - 1][4]
}

京ICP备2022027737号
Copyright © 2022 - present @wangxiang

  • ☀️
  • 🌑