LeetCode 309. Best Time to Buy and Sell Stock with Cooldown

题目

309. Best Time to Buy and Sell Stock with Cooldown

思路

动态规划,改成最优子结构:

方法一

buyOrSell[i]:表示第i天一定卖的最大利润
递推关系:
for (i = 1...N) {
    if i <= 3 {
        0...3中只能有一天买,一天卖,枚举最大max
    } else {
        1、中间可能有多天买卖,如只有第一天卖掉:
        buyOrSell[1] + price[i] - price[3...i-1],取max
        for (j = 1...i-3), 分别计算上式,取max
        2、还需要计算price[i]-price[0...2],因为之前没有考虑在第02天买入,直接在第i天卖出,取max
    }
    这样就算出了buyOrSell[i],但是还要循环计算全部0...
}

方法一源码(Leetcode超时)

package cn.mitsuhide.leetcode;

public class Leetcode309 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Leetcode309 lc = new Leetcode309();
        int [] prices = {3, 8, 1, 6, 5, 20};
        System.out.println(lc.maxProfit(prices));
    }

    public int maxProfit(int[] prices) {
        if (prices.length <= 1) return 0;
        int N = prices.length;
        int [] buyOrSell = new int [N];
        int finalMax = 0;
        for (int i = 1; i < N; i++) {
                int max = 0;
                if (i <= 3) {
                    for (int j = 0; j < i; j++) {
                        max = Math.max(max, prices[i] - prices[j]);
                    }
                } else {
                    for (int j = 1; j <= i - 3; j++) {
                        for (int k = j + 2; k <= i - 1; k++) {
                            int addV = prices[i] > prices[k] ? prices[i] - prices[k] : 0;
                            max = Math.max(buyOrSell[j] + addV, max);
                        }
                    }
                    for (int j = 0; j <= 2; j++) {
                        max = Math.max(prices[i] - prices[j], max);
                    }
                }
                buyOrSell[i] = max;
                finalMax = Math.max(finalMax, max);
        }
        return finalMax;
    }
}

因为方法一超时,所以需要改进:

方法二(超时)

public int maxProfit(int[] prices) {
    if (prices.length <= 1) return 0;
    int N = prices.length;
    int [][] buyOrSell = new int [N][N];
    for (int j = 1; j < N; j++) {
        for (int i = 0; i + j < N; i++) {
            int max = 0;
            if (j <= 3) {
                for (int k = 0; k < j; k++) {
                    max = Math.max(max, prices[i + j] - prices[i + k]);
                }
            } else {
                for (int k = i + 1; k <= i + j - 3; k++) {
                    int addV = prices[k] > prices[i] ? prices[k] - prices[i] : 0;
                    max = Math.max(addV + buyOrSell[k + 2][i + j], max);
                }
                max = Math.max(prices[i + j] - prices[i], max);
            }
            buyOrSell[i][i + j] = max;
        }
    }
    int max = 0;
    for (int i = 1; i < N; i++) {
        max = Math.max(max, buyOrSell[0][i]);
    }
    return max;
}

方法三(超时)

public int maxProfit(int[] prices) {
    if (prices.length <= 1) return 0;
    int N = prices.length;
    int [] buyOrSell = new int [N];
    int [] min = new int[N];
    int finalMax = 0;
    min[0] = prices[0];
    for (int i = 1; i < N; i++) {
        min[i] =  prices[i] < min[i - 1] ? prices[i] : min[i - 1];
    }

    for (int i = 1; i < N ; i++) {
        if (i <= 3) {
            buyOrSell[i] = prices[i] > min[i-1] ? prices[i] - min[i-1] : 0;
        } else {
            buyOrSell[i] = 0;
            for (int j = 1; j <= i - 3; j++) {
                int tMin;
                if (min[i] < min[j + 1]) {
                    tMin = min[i];
                } else {
                    tMin = prices[j + 2];
                    for (int k = j + 2; k <= i; k++) {
                        tMin = Math.min(tMin, prices[k]);
                    }
                }
                buyOrSell[i] = Math.max(buyOrSell[j] + prices[i] - tMin, buyOrSell[i]);
            }
            buyOrSell[i] = Math.max(prices[i] - min[i-1], buyOrSell[i]);
        }
        finalMax = Math.max(finalMax, buyOrSell[i]);
    }
    return finalMax;
}

Accepted

苦逼的饶了很久,答案是这么写的:

package cn.mitsuhide.leetcode;

public class Leetcode309 {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Leetcode309 lc = new Leetcode309();
        int [] prices = {1, 2, 4};
        System.out.println(lc.maxProfit(prices));
    }

    public int maxProfit(int[] prices) {
        if (prices.length <= 1) return 0;
        int N = prices.length;
        int [] sell = new int [N];
        int [] coolDown = new int[N];
        sell[1] = prices[1] - prices[0];
        coolDown[0] = coolDown[1] = 0;
        for (int i = 2; i < N; i++) {
            coolDown[i] = Math.max(sell[i - 1], coolDown[i - 1]);
            sell[i] = Math.max(sell[i - 1], coolDown[i - 2]) + prices[i] - prices[i - 1];
        }
        return Math.max(sell[N - 1], coolDown[N - 1]);
    }

}

Accept的思路

假设从0…i-1天已经是买卖完成(正好把买来的卖完,从0一开始也是这样)或者是已买入,等待卖出,考虑的是第i天的状态:
1、第i天是coolDown状态,那么前一天只能是sell、coolDown、buy

2、第i天是sell状态,那么前一天只能是buy、coolDown
这里注意前一天如果是coolDown状态,那么说明再往前一定有最后一次buy状态,还未来得及卖出,正好在第i天卖出。

3、第i天是buy状态,那么前一天只能是coolDown
所以我们用三个数组来计算最大利润:

coolDown[i]:第i天是coolDown状态时的最大利润,
sell[i]:第i天是sell状态时的最大利润,
buy[i]:第i天是buy状态时的最大利润,

由此可以得到状态递推关系:

1、coolDown[i] = max(sell[i - 1],coolDown[i - 1], buy[i - 1]);
2、sell[i] = max(buy[i - 1] + price[i] - price[i - 1], coolDown[i - 1] + price[i] - price[之前最后一次的买入天]);
3、buy[i] = coolDown[i - 1];

将3式子带入12,得到:
1、coolDown[i] = max(sell[i - 1], coolDown[i - 1], coolDown[i - 2];
2、sell[i] = max(coolDown[i - 2] + price[i] - price[i - 1], coolDown[i - 1] + price[i] - price[之前最后一次的买入天]);

可以发现,只需要两个数组sell和coolDown就可以了。
但是从2式中看到,还需要记录之前的状态,否则计算不出“之前最后一次的买入天”。

这里也正是accept答案最巧妙的地方,一般人很难注意到2中的下式成立:

coolDown[i - 1] + price[i] - price[之前最后一次的买入天] = sell[i - 1] + price[i] - price[i - 1];

这是整个accept答案中最难想到的递推式。
这样想:
2式的意思是在第i天卖出,能卖出的最大利润是多少???其实将sell[i]和sell[i-1]作比较,会发现:

sell[i - 1]的状态:
0   ...   k  ...  i-1, i
.   ...  buy ...  sell, coolDown
.   ...  buy ...  coolDown, sell

可以发现对于sell[i] 和 sell[i - 1],0…i - 2天的状态只能是完全相同的,不同的只有最后一天卖出的价格,sell[i]是以price[i]卖出,sell[i-1]是以price[i-1]卖出。
所以,sell[i]比sell[i-1]多卖出的价格 = price[i] - price[i-1], 也就是(太关键了!):

sell[i] - sell[i-1] = price[i] - price[i-1] 
=>
sell[i] = sell[i-1] + price[i] - price[i-1] 

这样,我们的递推式就是:

1、coolDown[i] = max(sell[i - 1], coolDown[i - 1], coolDown[i - 2];
2、sell[i] = max(coolDown[i - 2] + price[i] - price[i - 1], sell[i-1] + price[i] - price[i-1]);

答案中把2式子简化成了(太精辟导致一开始根本看不懂):

sell[i] = max(coolDown[i - 2],sell[i-1]) + price[i] - price[i-1];

在跟上初始化的值:

sell[1] = price[1] - price[0];
coolDown[0] = coolDown[1] = 0;

源码见上文。

总结

虽然这题一眼能看出是用动态规划,但是也不能乱用,一定要想清楚递推关系,找到问题的根本,设计出最简单、最快的算法!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mitsuhide1992

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值