LeetCode 188. Best Time to Buy and Sell Stock IV(股票买卖)

37 篇文章 0 订阅
36 篇文章 0 订阅

原题网址:https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iv/

Say you have an array for which the ith element is the price of a given stock on day i.

Design an algorithm to find the maximum profit. You may complete at most k transactions.

Note:
You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).

思路:定义两个二维变量,

last[j][i],表示恰好在第j日完成第i次交易的最大收益。

total[j][i],表示在第j日之前(含)完成i次交易的最大收益。

那么如何递归或者递推计算两个变量的值呢?我们先考虑total变量,第j日之前完成i次交易,可以分为两种情况,第一种情况是最后一日不作任何交易,第二种是最后一日完成第i次交易,则total[j][i] = max(total[j-1][i], last[j][i]),这个比较容易理解。如何计算last呢?我们可以按照倒数第二日的交易情况进行分类,分为倒数第二日完成第i次交易,以及倒数第二日不做任何交易。对于前者,我们可以观察如果倒数第二日的第i次交易推迟到第i日的获利情况;对于后者,我们可以观察倒数第二日买入,最后一日(第j日)卖出的情况,即:last[j][i] = max(0, last[j-1][i] + prices[j] - prices[j-1], total[j-1][i-1] + prices[j] - prices[j-1])。为什么会有0呢?因为我们的交易至少不能亏钱,如果一定要有交易的话,我们当天买入、当天卖出,至少是可以不亏的。但会不会有其他情况呢?例如最后一次交易有没有可能是倒数第三天买入,最后一天卖出?分析下面六种情况,可以知道公式是正确的。


数据流演示:


方法一:动态规划。

public class Solution {
    private int max(int[] prices) {
        int max = 0;
        for(int i=1; i<prices.length; i++) {
            max += Math.max(0, prices[i] - prices[i-1]);
        }
        return max;
    }
    public int maxProfit(int k, int[] prices) {
        if (prices == null || prices.length < 2) return 0;
        int n = prices.length;
        if (k >= n/2) return max(prices);
        int[][] last = new int[n][k+1];
        int[][] total = new int[n][k+1];
        for(int t = 1; t <= k; t ++) {
            for(int d = 1; d < n; d ++) {
                last[d][t] = Math.max(last[d-1][t] + prices[d] - prices[d-1], total[d-1][t-1] + Math.max(0, prices[d] - prices[d-1]));
                total[d][t] = Math.max(total[d-1][t], last[d][t]);
            }
        }
        return total[n-1][k];
    }
}

方法二:在方法一的基础上,优化内存分配。

public class Solution {
    private int max(int[] prices) {
        int max = 0;
        for(int i=1; i<prices.length; i++) {
            int p = prices[i] - prices[i-1];
            if (p>0) max += p;
        }
        return max;
    }
    public int maxProfit(int k, int[] prices) {
        if (prices == null || prices.length < 2) return 0;
        int n = prices.length;
        if (k >= n/2) return max(prices);
        int[] last = new int[n];
        int[] total = new int[n];
        int[] prevTotal = new int[n];
        for(int t = 1; t <= k; t ++) {
            int[] tempTotal = prevTotal;
            prevTotal = total;
            total = tempTotal;
            for(int d = 1; d < n; d ++) {
                last[d] = Math.max(last[d-1] + prices[d] - prices[d-1], prevTotal[d-1] + Math.max(0, prices[d] - prices[d-1]));
                total[d] = Math.max(total[d-1], last[d]);
            }
        }
        return total[n-1];
    }
}

这里有个诀窍,其实临时变量prevTotal可以省略:

public class Solution {
    private int max(int[] prices) {
        int max = 0;
        for(int i=1; i<prices.length; i++) {
            int p = prices[i] - prices[i-1];
            if (p>0) max += p;
        }
        return max;
    }
    public int maxProfit(int k, int[] prices) {
        if (prices == null || prices.length < 2) return 0;
        int n = prices.length;
        if (k >= n/2) return max(prices);
        int[] last = new int[n];
        int[] total = new int[n];
        for(int t = 1; t <= k; t ++) {
            for(int d = 1; d < n; d ++) {
                last[d] = Math.max(last[d-1] + prices[d] - prices[d-1], total[d-1] + Math.max(0, prices[d] - prices[d-1]));
            }
            for(int d = 1; d < n; d ++) {
                total[d] = Math.max(total[d-1], last[d]);
            }
        }
        return total[n-1];
    }
}


方法三:同样是动态规划,但是用日期作为外层循环,把交易作为内层循环:

public class Solution {
    private int max(int[] prices) {
        int max = 0;
        for(int i=1; i<prices.length; i++) {
            max += Math.max(0, prices[i] - prices[i-1]);
        }
        return max;
    }
    public int maxProfit(int k, int[] prices) {
        if (prices == null || prices.length < 2) return 0;
        int n = prices.length;
        if (k >= n/2) return max(prices);
        int[][] last = new int[k+1][n];
        int[][] total = new int[k+1][n];
        for(int d = 1; d < n; d ++) {
            int diff = prices[d] - prices[d-1];
            for(int t = 1; t <= k; t ++) {
                last[t][d] = Math.max(0, last[t][d-1] + diff);
                last[t][d] = Math.max(last[t][d], total[t-1][d-1] + Math.max(0, diff));
                total[t][d] = Math.max(total[t][d-1], last[t][d]);
            }
        }
        return total[k][n-1];
    }
}

方法四:在方法三的基础上优化内存,这里有个诀窍,只需要让t从k到1倒过来,就可以节省上一层循环的内存空间!

public class Solution {
    private int max(int[] prices) {
        int max = 0;
        for(int i=1; i<prices.length; i++) {
            max += Math.max(0, prices[i] - prices[i-1]);
        }
        return max;
    }
    public int maxProfit(int k, int[] prices) {
        if (prices == null || prices.length < 2) return 0;
        int n = prices.length;
        if (k >= n/2) return max(prices);
        int[] last = new int[k+1];
        int[] total = new int[k+1];
        for(int d = 1; d < n; d ++) {
            int diff = prices[d] - prices[d-1];
            for(int t = k; t >= 1; t --) {
                last[t] = Math.max(0, last[t] + diff);
                last[t] = Math.max(last[t], total[t-1] + Math.max(0, diff));
                total[t] = Math.max(total[t], last[t]);
            }
        }
        return total[k];
    }
}

方法五:采用最大堆来对波段的盈利进行排序,使用栈来保持未能确定操作方式的波段。我没有想出来,参考网上的: https://leetcode.com/discuss/26745/c-solution-with-o-n-klgn-time-using-max-heap-and-stack


public class Solution {
    private int max(int[] prices) {
        int max = 0;
        for(int i=1; i<prices.length; i++) {
            int p = prices[i] - prices[i-1];
            if (p>0) max += p;
        }
        return max;
    }
    public int maxProfit(int k, int[] prices) {
        if (prices == null || prices.length < 2) return 0;
        int n = prices.length;
        if (k >= n/2) return max(prices);
        PriorityQueue<Integer> profits = new PriorityQueue<>(Collections.reverseOrder());
        Stack<int[]> waves = new Stack<>();
        int v = 0, p = 0;
        while (p < n) {
            for(v = p; v < n - 1 && prices[v] >= prices[v+1]; v ++);
            for(p = v + 1; p < n && prices[p] >= prices[p-1]; p ++);
            while (!waves.isEmpty()) {
                int[] wave = waves.peek();
                if (prices[v] >= prices[wave[0]]) break;
                profits.offer(prices[wave[1]-1]-prices[wave[0]]);
                waves.pop();
            }
            while (!waves.isEmpty()) {
                int[] wave = waves.peek();
                if (prices[p-1] < prices[wave[1]-1]) break;
                profits.offer(prices[wave[1]-1]-prices[v]);
                v = wave[0];
                waves.pop();
            }
            waves.push(new int[] {v, p});
        }
        while (!waves.isEmpty()) {
            int[] wave = waves.pop();
            profits.offer(prices[wave[1]-1]-prices[wave[0]]);
        }
        int max = 0;
        for(int i = 0; i < k && !profits.isEmpty(); i ++) {
            max += profits.poll();
        }
        return max;
    }
}

方法六:神奇的方法,网上看来的,一开始没有弄明白。

public class Solution {
    public int maxProfit(int k, int[] prices) {
        if (k < 1 || prices == null || prices.length < 2) return 0;
        if (k >= prices.length/2) {
            int max = 0;
            for(int i=1; i<prices.length; i++) max += Math.max(0, prices[i]-prices[i-1]);
            return max;
        }
        int[] buy = new int[k];
        int[] sell = new int[k];
        Arrays.fill(buy, Integer.MIN_VALUE);
        for(int i=0; i<prices.length; i++) {
            for(int j=0; j<k; j++) {
                buy[j] = Math.max(buy[j], j==0?-prices[i]:sell[j-1]-prices[i]);
                sell[j] = Math.max(sell[j], buy[j]+prices[i]);
            }
        }
        return sell[k-1];
    }
}

然后分析上面代码,发现和下面是等价的,看起来就比较像动态规划了。

public class Solution {
    public int maxProfit(int k, int[] prices) {
        if (k <= 0 || prices.length < 2) return 0;
        int n = prices.length;
        if (k >= n / 2) {
            int max = 0;
            for(int i = 1; i < n; i++) {
                max += Math.max(0, prices[i] - prices[i - 1]);
            }
            return max;
        }
        
        int[][] buy = new int[prices.length+1][k+1];  
        int[][] sell = new int[prices.length+1][k+1];  
        Arrays.fill(buy[0], Integer.MIN_VALUE);  
          
        for(int i=1; i<=prices.length; i++) {  
            for(int j=1; j<=k; j++) {  
                buy[i][j] = Math.max(buy[i-1][j], sell[i][j-1]-prices[i-1]);
                sell[i][j] = Math.max(sell[i-1][j], buy[i][j]+prices[i-1]);
            }  
        }  
        return sell[prices.length][k];          
    }
}
神奇的是,上面的i和j循环可以互换,不影响结果!

看数据流,可以帮助理解这个算法:


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值