一天一大 leet(最佳买卖股票时机含冷冻期)难度:中等-Day20200710

img

题目:

给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。​

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

  • 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
  • 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
示例
输入: [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]

抛砖引玉

img

  • 第一天收益设置为 -prices[0],作为成本(即成本也纳入收益计算)
  • 第 i 天价格为 prices[i]
  • 冷冻期 如果这一天为冷冻期说明为前一天卖出,则这一天的收益为:
    • 前一天持有时的最大收益+卖出的盈利
    • dp[i-1][持有] + prices[i]
  • 不持有 如果这一天不持有则前一天可能是不持有或者为冷冻期(一定不是持有),则这一天收益理论上不变沿用前一天的收益
    计算收益最大则取两周可能中较大的收益 max:
    • max(dp[i-1][不持有], dp[i-1][冷冻])
  • 持有 如果这一天为持有,则前一天可能是持有也可能为不持有(一定不是冷冻),则这一天收益:
    • 注意 前一天如果为不持有则说明今天买入后才持有则按照计入成本规则需要减掉今日的成本
    • max(前一天持有时的最大收益, 前一天不持有时的最大收益-今日成本)
    • max(dp[i-1][持有], dp[i-1][不持有]-prices[i])

示例:[1,2,3,0,2]

价格 p(i)\收益 dp[i]i=0,p[0]=1i=1,p[1]=2i=2,p[2]=3i
持有 00-1max(0-1,0-2)max(-1,0-3)max(dp[i-1][0], dp[i-1][2] - p[i])
冷冻期 1-0-1+2-1+3dp[i-1][0]+p[i]
不持有 20max(0,-)max(0,1)max(dp[i-1][2], dp[i-1][1])

最后如果计算收益最大,则结束时状态不能为持有

/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function (prices) {
  let len = prices.length,
    dp = Array(len)
  // prices小于2是买入来不及卖出
  if (len < 2) return 0
  // 初始化存储对象
  for (let i = 0; i < len; i++) {
    dp[i] = [0, 0, 0]
  }
  // 初始化成本
  dp[0][0] = -prices[0]
  for (let i = 1; i < len; i++) {
    dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][2] - prices[i])
    dp[i][1] = dp[i - 1][0] + prices[i]
    dp[i][2] = Math.max(dp[i - 1][1], dp[i - 1][2])
  }
  return Math.max(dp[len - 1][1], dp[len - 1][2])
}

优化:

  1. 存储降维
  • 从上面的逻辑可以发现当 i 递增时,计算当前 i 的不同状态收益只需要 i-1 不同状态收益
  • 那可以声明一个中间变量来存贮 i-1 的状态就可以替代 dp 的作用了
/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function (prices) {
  let len = prices.length
  // prices小于2是买入来不及卖出
  if (len < 2) return 0
  // 初始化存储对象
  let A = -prices[0],
    B = 0,
    C = 0
  // 初始化成本
  for (let i = 1; i < len; i++) {
    let a = Math.max(A, C - prices[i])
    let b = A + prices[i]
    let c = Math.max(B, C)
    A = a
    B = b
    C = c
  }
  return Math.max(B, C)
}
  1. 状态归纳
  • 0 -> 持有
  • 1 -> 冷冻期
  • 2 -> 不持有

可以归纳为:

  • 0 -> 持有 hold
  • 1 -> 不持有(不持有,冷冻期) unhold

持有:

  • 第一天成本 -prices[0]

  • i=1 第二天持有,有两种可能(前一天不存在持有冷冻情况):

    • 第一天买入:hold[i - 1] 及 -prices[0]
    • 第二天买入:hold[i - 1] -prices[1],-prices[0]-prices[1]
  • i 大于 1 持有时(前一天存在持有冷冻情况):

    • 第 i-1 天持有:hold[i - 1]
    • 第 i-1 天不持有:
      • 为冷冻,则说明第 i-2 天卖出,第 i 天买入:
        第 i-2 天不持有的收益减去第 i 天买入的成本
        unhold[i - 2]-prices[i]
      • 不为冷冻 则最近的一次最大收益应为 hold[i - 2]小于 hold[i - 1]忽略

不持有:

  • 第 n-1 天有两种可能:
    • 持有,今天不持有卖出今天的价格算入收益:hold[i - 1] + prices[i]
    • 不持有,无操作, 其最大收益保持不变
/**
 * @param {number[]} prices
 * @return {number}
 */
var maxProfit = function (prices) {
  let len = prices.length
  // prices小于2是买入来不及卖出
  if (len < 2) return 0
  let hold = Array(len),
    unhold = Array(len)
  // 初始化存储对象
  hold[0] = -prices[0]
  unhold[0] = 0
  for (let i = 1; i < len; i++) {
    if (i == 1) {
      hold[i] = Math.max(-prices[0], -prices[1])
    } else {
      hold[i] = Math.max(hold[i - 1], unhold[i - 2] - prices[i], unhold[i - 1])
    }
    unhold[i] = Math.max(unhold[i - 1], hold[i - 1] + prices[i])
  }
  return unhold[len - 1]
}
探险家小扣的行动轨迹,都将保存在记录仪中。expeditions[i] 表示小扣第 i 次探险记录,用一个字符串数组表示。其中的每个「营地」由大小写字母组成,通过子串 -> 连接。例:"Leet->code->Campsite",表示到访了 "Leet"、"code"、"Campsite" 三个营地。expeditions[0] 包了初始小扣已知的所有营地;对于之后的第 i 次探险(即 expeditions[i] 且 i > 0),如果记录中包了之前均没出现的营地,则表示小扣 新发现 的营地。 请你找出小扣发现新营地最多且索引最小的那次探险,并返回对应的记录索引。如果所有探险记录都没有发现新的营地,返回 -1。注意: 大小写不同的营地视为不同的营地; 营地的名称长度均大于 0。用python实现。给你几个例子:示例 1: 输入:expeditions = ["leet->code","leet->code->Campsite->Leet","leet->code->leet->courier"] 输出:1 解释: 初始已知的所有营地为 "leet" 和 "code" 第 1 次,到访了 "leet"、"code"、"Campsite"、"Leet",新发现营地 2 处:"Campsite"、"Leet" 第 2 次,到访了 "leet"、"code"、"courier",新发现营地 1 处:"courier" 第 1 次探险发现的新营地数量最多,因此返回 1。示例 2: 输入:expeditions = ["Alice->Dex","","Dex"] 输出:-1 解释: 初始已知的所有营地为 "Alice" 和 "Dex" 第 1 次,未到访任何营地; 第 2 次,到访了 "Dex",未新发现营地; 因为两次探险均未发现新的营地,返回 -1
04-23
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值