【必备算法】动态规划:一个思路解决六道股票问题

买卖股票系列一共有6道题,都是不断交易一只股票(股价通过price[]给出,数组长度表示可以交易的天数)然后求最大利润:

  • 121. 买卖股票的最佳时机¹:股票只能交易一次
  • 122. 买卖股票的最佳时机 II¹:股票可以交易无数次
  • 123. 买卖股票的最佳时机 III³:股票可以交易2次
  • 188. 买卖股票的最佳时机 IV³:股票可以交易K次
  • 309. 最佳买卖股票时机含冷冻期²:可以交易无数次,但完成一笔交易后有一天冷却期
  • 714. 买卖股票的最佳含手续费²:可以交易无数次,但完成一笔交易后需要交手续费

PS:交易一次的意思是一买一卖,也可以理解成卖出次数

现在我们要通过一种动态规划的思路去通解上面六道题!


我们都知道,动态规划的第一步是状态定义,当成功且合适的定义了状态,一道动态规划问题就解决一半了。那上面的股票问题如何定义状态呢?

因为最后求的都是最大利润,所以状态的含义肯定是maxprofit。但应该定义成几维状态呢?我们下面就从一维到三维逐一尝试…

1.一维状态:maxprofit[i]

  • 状态定义:MP[i]。第i天的最大利润,取值[0, n)
  • 状态递推:对于股票无非就两种操作,买入和卖出
    • 买入:MP[i] = MP[i - 1] + (-p[i])
    • 卖出:MP[i] = MP[i - 1] + (p[i])
  • 最终状态:MP[n - 1]。第n天的最大利润

问题:因为只能持有一股,所以买入和卖出的前提分别是未持有和持有,但是只用这一位 i,根本就没法知道是否持有啊,所以无法正确进行状态递推,所以只用一维不太行…

2.二维状态:maxprofit[i][j]

  • 状态定义:MP[i][j] 。第 i 天未持有股票(j=0)或持有股票(j=1)的最大利润
    • i表示第i天,取值[0, n)
    • j表示当前天是否持有股票,取值为 0或1
  • 状态递推:因为是二维状态,所以照理说 i、j 是嵌套循环关系,但由于 j 只有01两种情况,所以直接枚举出来了:
    • MP[i][0]:我第 i 天没有股票。
      ==>MP[i][0] = max(MP[i-1][0], MP[i - 1][1] + p[i])
      • MP[i][0] = MP[i-1][0] ,我前一天没股票且今天不操作
      • MP[i][0] = MP[i - 1][1] + p[i],我前一天有股票然后卖出了
    • MP[i][1]:我第 i 天有股股票。
      ==>MP[i][1] = max(MP[i-1][1], MP[i - 1][0] - p[i])
      • MP[i][1] = MP[i-1][1] ,我前一天持有股票但今天我不操作
      • MP[i][1] = MP[i - 1][0] - p[i] ,我前一天没有股票今天买入了
  • 最终状态:MP[n-1][0]。i=n-1没什么说的,j=0是因为市场都熄火了,你还持有着股票(j=1)肯定是不值的,就算亏了也要卖掉回点老本

问题一:上面说的是交易无限次(122题),那如果是限制只能交易1次(121题)怎么搞?
答:可以这么想,如果是允许交易无数次,那么我们在买入股票时的最大利润受前面交易结果的影响,即maxprofit=MP[i-1][0]-p[i];但如果只能交易一次,我买入了股票就是只用减去花的钱,不考虑之前的状态(MP[i-1][0]),即maxprofi=-p[i]
所以,只能交易一次的状态定义不用变,只是在递推时MP[i][1] = max(MP[i-1][1], - p[i])

问题二:交易一次用二维状态够了,那如果是限制交易次数为2次(123题)甚至变量k次(188题)怎么搞?
答:这个…好像二维状态携带的信息不够进行状态递推了,还需要加一维去保存交易几次了。所以,二维状态虽然可行,但是不够通用…

3.三维状态:maxprofit[i][k][j]

  • 状态定义:MP[i][k][j] 。表数第 i 天且已经交易了k次的时,未持有和持有股票的最大利润

    • i表示第i天,取值[0, n)
    • k表示到今天(第i天)已经交易了k次(注:这里说的是已经卖出了k次),取值 [0, K]
    • j表示当前天是否持有股票,取值为 0或1
  • 状态递推:因为是三维状态,所以照理说要 i,k,j 三层嵌套循环,但由于 j 只有01两种情况,所以 j 可以直接枚举:

    • MP[i][k][0]:我第 i 天没有股票且已经交易 k 次 。
      ==>MP[i][k][0] = max(MP[i-1][k][0], MP[i - 1][k-1][1] + p[i])

      • MP[i][k][0] = MP[i-1][k][0],我前一天没股票且今天不操作(k不用变,因为今天没操作)

      • MP[i][k][0] = MP[i - 1][k-1][1]+ p[i],我前一天有股票然后卖出了(k要减一,因为今天卖出了)

    • MP[i][k][1]:我第 i 天持有股票且已经交易 k 次。
      ==>MP[i][k][1] = max(MP[i-1][k][1], MP[i - 1][k][0] - p[i])

      • MP[i][k][1] = MP[i-1][k][1],我前一天没股票且今天不操作(k不用变,因为今天没操作)

      • MP[i][k][1] = MP[i - 1][k][0] - p[i],我前一天没有股票今天买入了(k不用变,因为这是买入不是卖出,是在一次交易内的(切记))

  • 最终状态:MP[n-1][K][0]

问题一:在状态递推时可以将 j 直接列出,那 k 如何处理?
答:分成两种情况

  • 123题中 K=2,再结合上 j 具体就是6种状态,所以可以直接枚举出来。mp[i][0][0],mp[i][0][1],mp[i][1][0],mp[i][1][1],mp[i][2][0],mp[i][2][0]
  • 188题中 k 是变量形式给出的,所以就需要遍历 k。时间复杂度O(N*K*2),空间复杂度也是O(N*K*2)

问题二:那这以交易次数作为条件的我们分析完了,但如果是有冷却期的(309题)或者说收手续费(714题)怎么搞?
答:这个两个题都是不限制交易次数的,所以二维状态就够了,只不过在状态递推时有所不同。具体请看后面题解…

好了,思路分析就到这里,具体动态规划代码请看:

  • 【必备算法】动态规划:LeetCode(七)121. 买卖股票的最佳时机,122. 买卖股票的最佳时机 II,123. 买卖股票的最佳时机 III,188. 买卖股票的最佳时机 IV
  • 【必备算法】动态规划:LeetCode(八),309. 最佳买卖股票时机含冷冻期,714. 买卖股票的最佳含手续费

你可能感兴趣的:(#,必备算法,动态规划,java)