动态规划买卖股票终极问题--买卖k次的最大利润的初始化的理解

188. 买卖股票的最佳时机 IV - 力扣(Leetcode)

做动态规划的方法(算法原理)是:1.状态表示 2.状态转移方程 3.初始化 4.填表顺序 5.返回值

这篇文章主要讲关于这道题的简化代码的理解,即对初始化的简化的理解;

本文最终的结论是 最简的代码的一部分初始化可能失去了原本的意义,但是恰好不影响最终结果

(可以略读,直接读我高亮的地方)

(对第一次买赋初值的理解太重要了)


突然发现全部初始化为-3F3F3F3F即可,这个就非常好想了。。

所以本篇文章实质是对"不合适"的初始化进行了大量的探索。。


目录

1.推状态转移方程

2.初始化

f[0][0],g[0][0]的理解:

f[0][j],g[0][j]的理解:

3.通过代码:


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;
    }
};

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值