188. 买卖股票的最佳时机 IV - 力扣(Leetcode)
做动态规划的方法(算法原理)是:1.状态表示 2.状态转移方程 3.初始化 4.填表顺序 5.返回值
这篇文章主要讲关于这道题的简化代码的理解,即对初始化的简化的理解;
本文最终的结论是 最简的代码的一部分初始化可能失去了原本的意义,但是恰好不影响最终结果
(可以略读,直接读我高亮的地方)
(对第一次买赋初值的理解太重要了)
突然发现全部初始化为-3F3F3F3F即可,这个就非常好想了。。
所以本篇文章实质是对"不合适"的初始化进行了大量的探索。。
目录
1.推状态转移方程
咱们开两个数组分别表示 第i天买股票和卖股票 的最大利润。(f[][] g[][])
把两个数组都开成二维数组,就可以表示买卖第j次啦 (f[i][j])
关系:
(根据咱们前面对数组的定义,每次卖完后j++,即次数增加 (第一次卖完后下一次就是第二次买啦))
所以得出状态转移方程:
(prices是第i天的股票价格,因为我们为代码简洁,初始化对f和g都多开了第一行,所以第1天是i==1,对应prices[0],所以是prices[i-1])
f[i][j] = max(g[i-1][j-1] - prices[i-1],f[i-1][j]); 【 max(买,不买) 】
g[i][j] = max(g[i-1][j],f[i-1][j] + prices[i-1]); 【 max(不卖,卖) 】
因为是j-1,存在越界问题,所以对j == 0时做特殊处理,就是把g[i-1][j-1]换为零了,因为不存在第零次卖,即第一次买的时候,手上的利润为零
f[i][0] = max(- prices[i-1],f[i-1][0]);
g[i][0] = max(g[i-1][0],f[i-1][0] + prices[i-1]);
2.初始化
然而初始化是无情的,因为这是对开始的特殊处理(同时因为状态方程可以简化,需要做处理了),即对特殊情况的处理。所以有的时候没有特别的含义,就是为了让开始的值能跑起来。
因为对第一行初始化和状态转移方程可以兼容,所以我们在最前面多开一行,对第一行设置特殊数值使第一天的初始化用状态转移方程也能正确,这样代码可以简化
对多开的一行的处理:
int hmax = 0x3f3f3f3f;这是个相当大的负数,同时不易产生越界问题,算法题会用到
g[0][0]本来就要初始化为0(下面有解释),vector已经初始化好了(默认初始化为0),所以没写
f[0][0] = -prices[0];
for(int j = 1;j<k;j++)
{
f[0][j] = -hmax;
g[0][j] = -hmax;
}
f[0][0],g[0][0]的理解:
(这里还没有对多开一行解释,即这里的f[0][0],g[0][0]为之后的f[1][0],g[1][0])
当i = 1时,(即第一天的时候(prices[0]))买入的最大利润,
我们最后注定返回的是g卖出的某次。
如果0天不说了;
如果1天肯定不买,g[0]是0即可,所以跟f[0]没有关系;
如果2天的话那要么不买,要么第一天买了第二天卖了能赚呗,所以这里
g[1] = max(g[0],f[0]+prices[1]) 正好对应状态转移方程,这里要求f[0]为第一天买完后的钱,(我们可以认为f[0]不表示第一天买后的最大利润,因为你第一天买了会花钱,不如不买。。)
所以f[0][0] = 0 - prices[0];
g[0][0] = 0;
然而第0行我们用来初始化了,第1行即i==1才为第1天
所以
f[1][0] = 0 - prices[0];
g[1][0] = 0;
然而我们要用这个:
f[i][j] = max(g[i-1][j-1] - prices[i-1],f[i-1][j]);
g[i][j] = max(g[i-1][j],f[i-1][j] + prices[i-1]);
因为j==0,所以用的是:
f[i][0] = max(- prices[i-1],f[i-1][0]);
g[i][0] = max(g[i-1][0],f[i-1][0] + prices[i-1]);
即
f[1][0] = max(- prices[0],f[0][0]); 【 max(买,不买) 】
g[1][0] = max(g[0][0],f[0][0] + prices[0]);
我们希望 f[1][0] = 0 - prices[0]; g[1][0] = 0;
所以f[0][0]设为-prices[0](或者更小的数如-hmax都行)
g[0][0]设为0。
f[0][j],g[0][j]的理解:
说清楚特例,别的都同理了。
依旧围绕状态转移方程:
f[i][j] = max(g[i-1][j-1] - prices[i-1],f[i-1][j]);
g[i][j] = max(g[i-1][j],f[i-1][j] + prices[i-1]);
f[3][1]是第二次买最快的一天,那这里要不要买呢,如果你在想这个,那么就败了!这个定义应该同理我们“第一次买最快的一天”(即f[1][0]),是被要求定义为买的(至于为什么,看上面对f[1][0]的解释)所以我们希望f[3][1] = g[2][0] - prices[2] (其实不用状态转移方程的话这样初始化即可)
f[3][1] = max(g[2][0] - prices[2],f[2][1]);所以我们希望g[2][0] - prices[2] > f[2][1] ,f[2][1]是个非常小的数就好了
接着往前推
f[2][1] = max(g[1][0] - prices[1],f[1][1]);
g[1][0] = max(g[0][0],f[0][0] + prices[0]);
f[1][1] = max(g[0][0] - prices[1],f[0][1]);这里f[0][1]就到起源了。
代入一下:
f[2][1]=max( max(g[0][0],f[0][0] + prices[0]) - prices[1],max(g[0][0] - prices[1],f[0][1]));
=max(0-prices[1],max(-prices[1],f[0][1]));
=max(-prices[1],max(-prices[1],f[0][1]);
这里可以看出f[2][1]非-prices[1]即f[0][1],似乎f[2][1]最小就是-prices[1]了呀!
-prices[1]似乎不能保证我们选到前面的值
()
看下g[2][0] = max(g[1][0],f[1][0] + prices[1])
即为g[2][0] = max(0,-prices[0] + prices[1]) >=0
那么g[2][0] - prices[2] >= -prices[2]
但g[2][0] - prices[2]仍可能小于0!!
即刚才的-prices[1]无法满足我们的要求 ,但如果满足我们的大要求呢?即 即使我们初始化的不是希望的,但此题仍对,且代码得到了简化,那我们就可以这么做
如果f[2][1] = -prices[1]比g[2][0] - prices[2]小,那么就恰好达到我们的目的了
如果比它大,即-prices[1]比g[2][0] - prices[2]都大了,意思是第2天买了不卖都比第2天赚了第3天买 赚,那么我们强行买其实没有意义 ,即这里第二次买相当于第一次买了,数值和第一次买相同,它的意义不准确了,它没有价值了,它仍在需要它的时候可以发挥作用,但是不需要它时让它失去意义可以简化我们的代码(或者说正好可以)
即这里不初始化为我们希望的也是正确的,因为我们此刻是要不惜一切代价简化我们的代码
所以让f[2][1]等于-prices[1]吧,所以我们把f[0][1]设为最小值即为-hmax(确实很麻烦很绕,,但是能够复用状态转移方程)
f[0][2] ,f[0][j] (j>0)同理,
当j>1的时候,也不会出现g[0][j]:
f[5][2]为第三次买
f[5][2] = max(g[4][1]- prices[4],f[4][2]); (f[i][j] = max(g[i-1][j-1] - prices[i-1],f[i-1][j]);)
我们希望他等于g[4][1]- prices[4],同理g[2][0],它是>=0的,这也保证了我们返回的结果的正确
所以希望f[4][2]<=0
f[4][2] = max(g[3][1] - prices[3],f[3][2]);
接着推
g[3][1] = max(g[2][1],f[2][1] + prices[2]);
f[3][2] = max(g[2][1] - prices[2],f[2][2]);
g[2][1] = max(g[1][1],f[1][1] + prices[0]);
f[2][2] = max(g[1][1] - prices[1],f[1][2]);
f[1][2] = max(g[0][1] - prices[0],f[0][2]);
没有出现g[0][j]
那么g[0][j]应该没有任何影响了,为0即可(因为g是建立在f的基础上的,或是啥也不动为0(指每次刚开始的时候))
3.通过代码:
class Solution {
public:
int maxProfit(int k, vector<int>& prices)
{
int hmax = 0x3f3f3f3f;
int n = prices.size();
vector<vector<int>>f(n+1,vector<int>(k));
auto g = f;
f[0][0] = -hmax;
for(int j = 1;j<k;j++)
{
f[0][j] = -hmax;
//g[0][j] = -hmax;
}
for(int i=1;i<=n;i++)
{
f[i][0] = max(- prices[i-1],f[i-1][0]);
g[i][0] = max(g[i-1][0],f[i-1][0] + prices[i-1]);
for(int j=1;j<k;j++)
{
f[i][j] = max(g[i-1][j-1] - prices[i-1],f[i-1][j]);
g[i][j] = max(g[i-1][j],f[i-1][j] + prices[i-1]);
}
}
int ret = g[n][0];
for(int i=0;i<k;i++)
{
ret = max(ret,g[n][i]);
}
return ret;
}
};