Leetcode面试经典150题-188.买卖股票的最佳时机IV

 解法都在代码里,不懂就留言或者私信,这个稍微难点,我提供了两种解法

/**基本的动态规划求解的过程 */
    public static int maxProfit2(int k, int[] prices) {
        /**题目给的数组不会为null,这里习惯性的健壮性判断
         如果交易日小于2,不可能获得任何的利润 */
        if(prices == null || prices.length < 2) {
            return 0;
        }

        /**思路分析:本题只限制了一个交易数量,我们可以原地买卖,也就是说如果还没有凑够k的话,我们可以当天当天卖
         虽然这没有任何的意义,我们使用动态规划求解:dp数组的含义是dp[i][j]代表在0~i上做j次交易可以获得最大利润
         i的变化范围0~prices.length-1,j的变化范围0~k
         有一点需要注意:如果k大于prices.length/2,那就可以无限次买卖了,那就是题目2https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii
         毕竟我们的动态规划时间复杂度比较高,所以能不用还是不用了*/
        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;
        } else {
            int[][] dp = new int[prices.length][k+1];
            /**根据dp数组含义,最后我们要的是0~prices.length-1上做k次交易所能获得的最大利润*/
            /**我们先初始化第一行,也就是在0~0上做0~k次交易,同一天买同一天卖不可能有任何利润*/
            for(int j = 0; j <= k; j++) {
                dp[0][j] = 0;
            }
            /**第一列也可以先初始化了,也就是0~i上不做交易,不做交易当然是0了,其实int默认是0,第一行和第一列都不用初始化
             这样写看起来比较清晰*/
            for(int i = 0; i < prices.length; i++) {
                dp[i][0] = 0;
            }
            /**对于一般的情况,i和j的下标都从1开始 */
            for(int i = 1; i < prices.length; i++) {
                for(int j = 1; j <= k; j++) {
                    /**0~i上做j次交易有两种情况:1.前面0~i-1已经做了j次交易,这里直接等于dp[i-1][j]*/
                    int p1 = dp[i-1][j];
                    /**第二种情况;前面0~0,0~1...0~i-1做了j-1次交易,然后前面最后一次交易的那天买入,当前卖出 */
                    int p2 = 0;
                    for(int pre = 0; pre < i; pre ++) {
                        p2 = Math.max(p2, dp[pre][j-1] + prices[i] - prices[pre]);
                    }
                    dp[i][j] = Math.max(p1, p2);
                }
            }
            return dp[prices.length - 1][k];
        }
    }
    /**动态规划+斜率优化的解法,最优解*/
    public static int maxProfit(int k, int[] prices) {
        /**题目给的数组不会为null,这里习惯性的健壮性判断
         如果交易日小于2,不可能获得任何的利润 */
        if(prices == null || prices.length < 2) {
            return 0;
        }

        /**思路分析:本题只限制了一个交易数量,我们可以原地买卖,也就是说如果还没有凑够k的话,我们可以当天当天卖
         虽然这没有任何的意义,我们使用动态规划求解:dp数组的含义是dp[i][j]代表在0~i上做j次交易可以获得最大利润
         i的变化范围0~prices.length-1,j的变化范围0~k
         有一点需要注意:如果k大于prices.length/2,那就可以无限次买卖了,那就是题目2https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii
         毕竟我们的动态规划时间复杂度比较高,所以能不用还是不用了*/
        if(k > prices.length) {
            int max = 0;
            for(int i = 1; i < prices.length; i++) {
                max += Math.max(0, prices[i]-prices[i-1]);
            }
            return max;
        } else {
            int[][] dp = new int[prices.length][k+1];
            /**根据dp数组含义,最后我们要的是0~prices.length-1上做k次交易所能获得的最大利润*/
            /**我们先初始化第一行,也就是在0~0上做0~k次交易,同一天买同一天卖不可能有任何利润*/
            for(int j = 0; j <= k; j++) {
                dp[0][j] = 0;
            }
            /**第一列也可以先初始化了,也就是0~i上不做交易,不做交易当然是0了,其实int默认是0,第一行和第一列都不用初始化
             这样写看起来比较清晰*/
            for(int i = 0; i < prices.length; i++) {
                dp[i][0] = 0;
            }
            /**对于一般的情况,i和j的下标都从1开始 */
            for(int j = 1; j <= k; j++) {
                int preMax = 0;
                for(int i = 1; i < prices.length; i++) {
                    /**0~i上做j次交易有两种情况:1.前面0~i-1已经做了j次交易,这里直接等于dp[i-1][j],和基本动态规划相比,p1没变*/
                    int p1 = dp[i-1][j];
                    /**第二种情况;前面0~0,0~1...0~i-1做了j-1次交易,然后前面最后一次交易的那天买入,当前卖出
                     普通动态规划的解法我们有个枚举的过程,这里使用斜率优化给它省略了
                     dp[2][3]=Math.max(dp[0][2] + prices[2]-prices[0], dp[1][2]+price[2]-prices[1],dp[2][2]+price[2]-prices[2])
                     dp[3][3]=Math.max(dp[0][2] + prices[3]-prices[0], Math.max(dp[1][2]+price[3]-prices[1])), dp[2][2]+price[3]-prices[2])),dp[3][2]+prices[3]-prices[3])
                     这里我们可以发现的规律是dp[0][2]-prices[0]和dp[1][2]-prices[1]对于dp[2][3]和dp[3][3]都有
                     而dp[3][3]多出了dp[3][2]+prices[3]-prices[3]这一项(就是dp[3][2])
                     所以如果我们算dp[2][3]的时候知道dp[0][2]-prices[0]和dp[1][2]-prices[1]谁比较大,算dp[3][3]的时候直接用这个结果就行
                     那这个结果存在哪里了呢?dp[2][3]是这两个值的最大值+prices[2]算出来的,那这个值不就是dp[2][3]-prices[2]吗
                     所以我们可以得到的是dp[3][3]=Math.max(dp[2][3]-prices[2] +prices[3], dp[3][2])
                     抽象成一般位置dp[i][j]=Math.max(dp[i-1][j]-prices[i-1]+prices[i], dp[i][j-1])
                     这个其实也可以写成max(dp[i-1][j]-price[i-1],dp[i][j-1]-prices[i]) + prices[i]
                     前面的部分max(dp[i-1][j]-price[i-1],dp[i][j-1]-prices[i])我们叫它preMax
                     正常这么处理是没有什么问题的
                     这里举个特殊的例子,dp[2][1] = dp[0][0]+prices[2]-prices[0]  dp[1][0]+ prices[2]-prices[1] dp[2][0]
                     dp[1][1] = dp[0][0]+prices[1]-prices[0] dp[1][0]+prices[1]-prices[1]
                     dp[2][1] = Math.max(dp[i-1][j]-price[i-1],dp[i][j-1]-prices[i]) + prices[i]
                     但是对于dp[1][1]呢,它能等于max(dp[i-1][j]-price[i-1],dp[i][j-1]-prices[i]) + prices[i]吗
                     肯定不能啊,因为这里dp[0][j]-prices[0]+prices[1]也就是在0~0上交易了1次?然后又减去了prices[1]和prices[0]
                     的差价,这不是交易两次吗
                     */
                    int p2 = Math.max(i == 1? prices[i]-prices[i-1]: preMax+prices[i], dp[i][j-1]);
                    preMax = p2 - prices[i];
                    dp[i][j] = Math.max(p1, p2);
                }
            }
            return dp[prices.length - 1][k];
        }
    }

运行结果

e6860088515a469ea16c235c484b1351.jpg

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值