LeetCode股票问题通解(二)

关注公众号“算法岗从零到无穷”,并在微信后台回复“动态规划”,动态规划做法秘籍送给你。请帮忙分享给你的朋友喔!

今天我们继续股票问题,新来的朋友可以阅读我们上一则推送。我将告诉大家一个通用的方法,一个方法,解决6道股票问题。同时也要感谢leetcode用户fun4LeetCode,本文参考:https://urlify.cn/zYbu2a

我们的动态规划基本框架如下:

dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
解释:第i天我没有持有股票,有两种可能:
要么是我昨天就没有持有,然后今天选择“闲着”,
所以我今天还是没有持有;

要么是我昨天持有股票,但是今天我“卖了”,
所以我今天没有持有股票了。


dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
解释:第i天我持有着股票,有两种可能:
要么我昨天就持有着股票,然后今天选择“闲着”,
所以我今天还持有着股票;

要么我昨天本没有持有,但今天我选择“买入”,
所以今天我就持有股票了。

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

在这里插入图片描述
这道题中k = 2,和上期题目的情况稍微不同,因为上期题目最后都可以化简状态转移方程,与k无关。这道题无法化简,那么我们就把k的情况穷举出来,将状态转移方程直接翻译。

public int maxProfit(int[] prices) {
    int max_k = 2;
    int[][][] dp = newint[prices.length+1][max_k+1][2];
    for (int k=0;k<=max_k;k++) {
        dp[0][k][0]=0;
        dp[0][k][1]=Integer.MIN_VALUE;
    }
    for (int i = 1; i <= prices.length; i++) {
        for (int k = max_k; k >= 1; k--) {
            dp[i][k][0] = Math.max(dp[i-1][k][0], dp[i-1][k][1] + prices[i-1]);
            dp[i][k][1] = Math.max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i-1]);
        }
    }
    return dp[prices.length][max_k][0];
}

其实啊,这里 k 取值范围比较小,所以可以不用 for 循环,直接把 k = 1 和 2 的情况手动列举出来:

public int maxProfit(int[] prices) {
    int buyOne = Integer.MAX_VALUE;
    int SellOne = 0;
    int buyTwo = Integer.MAX_VALUE;
    int SellTwo = 0;
    for(int p : prices) {
        buyOne = Math.min(buyOne, p);
        SellOne = Math.max(SellOne, p - buyOne);
        buyTwo = Math.min(buyTwo, p - SellOne);
        SellTwo = Math.max(SellTwo, p - buyTwo);
    }
    return SellTwo;
}

上面这段代码大家都看过吧,是公认的正解。大家之前在阅读的时候,是不是很费解,这。。。这怎么想得到!那么现在用我们的框架一套,就很好理解了!

值得注意的是,这可是一道hard题,你看,跟着我们做算法,一不小心hard题也做出来了!

188. 买卖股票的最佳时机 IV

在这里插入图片描述
这道题是上面一道题的一般情况,也是一道hard题。很简单,我们把上面那道题的参数int maxK=2,改成int maxK=k就行了。但是程序过不了,内存会溢出。

我们简单分析一下,一次交易由买入和卖出构成,至少需要两天。所以说有效的限制 k 应该不超过 n/2,如果超过,就没有约束作用了,这种情况是之前解决过的,参见上一期推送的第122题。所以我们就单独那这个特例出来,按照上次的方式处理就ok了。代码如下:

int quickSolve(int[] prices) {
    int len = prices.length, profit = 0;
    for (int i = 1; i < len; i++)
        if (prices[i] > prices[i - 1])
            profit += prices[i] - prices[i - 1];
    return profit;
}
int maxProfit(int k, int[] prices) {
    int n = prices.length;
    if (k > n / 2)
        return quickSolve(prices);
    int[][][] dp = newint[n+1][k+1][2];
    for (int kk=0;kk<=k;kk++) {
        dp[0][kk][0]=0;
        dp[0][kk][1]=Integer.MIN_VALUE;
    }
    for (int i = 1; i <= n; i++) {
        for (int kk = k; kk >= 1; kk--) {
            dp[i][kk][0] = Math.max(dp[i - 1][kk][0], dp[i - 1][kk][1] + prices[i-1]);
            dp[i][kk][1] = Math.max(dp[i - 1][kk][1], dp[i - 1][kk - 1][0] - prices[i-1]);
        }
    }
    return dp[n][k][0];
}

值得注意的是,我们的代码没有经过优化,所以提交之后我们会发现效率并没有处在很前列,我们用这一个“股票系列”目的是想告诉大家一个通解的方法,优化动态规划的板块我们会和后面的章节一起讲,有兴趣的读者可以自己先尝试一下,找找规律。

在动态规划中,为了处理为0的边界条件,我们建议dp数组大小要比给出的大1,简单来说就是int[][][] dp = new int[n+1][k+1][2];这个代码的逻辑。

好了,这是我们动态规划的一大系列,动态规划还有一大系列是“背包问题”,不要着急,我们也会讲解。

关注公众号“算法岗从零到无穷”,并在微信后台回复“动态规划”,动态规划做法秘籍送给你。请帮忙分享给你的朋友喔!
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值