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

一、题目

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。

注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

输入: [2,4,1], k = 2
输出: 2
解释: 在第 1(股票价格 = 2) 的时候买入,在第 2(股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2

示例 2:

输入: [3,2,6,5,0,3], k = 2
输出: 7
解释: 在第 2(股票价格 = 2) 的时候买入,在第 3(股票价格 = 6) 的时候卖出, 这笔交易所能获得利润 = 6-2 = 4 。
     随后,在第 5(股票价格 = 0) 的时候买入,在第 6(股票价格 = 3) 的时候卖出, 这笔交易所能获得利润 = 3-0 = 3

二、 解决

系列:【LeetCode】121. 买卖股票的最佳时机【LeetCode】122. 买卖股票的最佳时机 II【LeetCode】123. 买卖股票的最佳时机 III

1、递归

思路:

1、状态定义:

M[day][cnt][status]含义: day: 表示天,取值0~n-1; 
                         cnt: 交易几次,取值0~k次;
                         status: 交易状态,表示是否拥有股票。 0-没有股票,只能买;1-拥有1股,只能卖。
2、状态方程
for day : 0-->n-1
  for cnt : 0-->k
  
      MP[day,cnt,0]:表示当天没有股票; 
      MP[day,cnt,1]:表示当天拥有一股。
      
      // 核心部分--状态方程
      MP[day,cnt,0] = max {
    
    
                        MP[day-1, cnt, 0]          // 不动 
                        MP[day-1, cnt-1, 1]+a[day] // 卖掉一股
                      }
    
      MP[day,cnt,1] = max {
    
    
                        MP[day-1,cnt,1]            // 不动
                        MP[day-1,cnt-1,0]-a[day]   // 买入一股
                      }

return MP[n-1, {
    
    0,...,k}, 0]:第n天且没有股票的时候,之前交易的最大值

代码:

class Solution {
    
    
    public int maxProfit(int k, int[] prices) {
    
    
        if(prices==null || prices.length==0) {
    
    
            return 0;
        }
        return dfs(0, 0, 0, k, prices);
    }
    // 计算k次交易,index表示当前是哪天,status是买卖状态,coutnt为交易次数
    private int dfs(int index, int status, int count, int k, int[] prices) {
    
    
        if(index==prices.length || count==k) {
    
    
            return 0;
        }
        int a=0,b=0,c=0;
        // 保持不动
        a = dfs(index+1, status, count, k, prices);
        if(status==1) {
    
    
            // 卖一股,并将交易次数+1
            b = dfs(index+1, 0, count+1, k, prices)+prices[index];
        } else {
    
    
            // 买一股
            c = dfs(index+1, 1, count, k, prices)-prices[index];
        }
        return Math.max(Math.max(a,b), c);
    }
}

时间复杂度: O ( 2 n ) O(2^n) O(2n)
空间复杂度: O ( l o g n ) O(logn) O(logn)

2、递归+记忆化

思路: 同上。

代码:

class Solution {
    
    
    public int maxProfit(int k, int[] prices) {
    
    
        if(prices==null || prices.length==0) {
    
    
            return 0;
        }
        int n = prices.length;
        //当k非常大时转为无限次交易
        if(k>=n/2) {
    
    
            int dp0=0,dp1=-prices[0];
            for(int i=1;i<n;++i) {
    
    
                int tmp = dp0;
                dp0 = Math.max(dp0,dp1+prices[i]);
                dp1 = Math.max(dp1,dp0-prices[i]);
            }
            return Math.max(dp0,dp1);
        }
        Map<Key,Integer> cache = new HashMap<Key,Integer>();
        return dfs(cache,0,0,0,k,prices);
    }

    //带记忆化的 计算k次交易,代码和递归版的一样只是前后加了缓存
    private int dfs(Map<Key,Integer> cache, int index, int status, int count, int k, int[] prices) {
    
    
        Key key = new Key(index,status,count);
        if(cache.containsKey(key)) {
    
    
            return cache.get(key);
        }
        if(index==prices.length || count==k) {
    
    
            return 0;
        }
        int a=0,b=0,c=0;
        a = dfs(cache,index+1,status,count,k,prices);
        if(status==1) {
    
    
            b = dfs(cache,index+1,0,count+1,k,prices)+prices[index];
        } else {
    
    
            c = dfs(cache,index+1,1,count,k,prices)-prices[index];
        }
        cache.put(key,Math.max(Math.max(a,b),c));
        return cache.get(key);
    }
    //Key对象封装了index、status、交易次数count,作为map的key
    private class Key {
    
    
        private final int index;
        private final int status;
        private final int count;
        Key(int index,int status,int count) {
    
    
            this.index = index;
            this.status = status;
            this.count = count;
        }
        //这里需要实现自定义的equals和hashCode函数
        public int hashCode() {
    
    
            return this.index + this.status + this.count;
        }
        public boolean equals(Object obj) {
    
    
            Key other = (Key)obj;
            if(index==other.index && status==other.status && count==other.count) {
    
    
                return true;
            }
            return false;
        }
    }
}

时间复杂度: O ( n 2 ) O(n^2) O(n2)
空间复杂度: O ( n ) O(n) O(n)

3、动态规划

版本1

思路:

同上,复制过来便于查看:

1、状态定义:

M[day][cnt][status]含义: day: 表示天,取值0~n-1; 
                         cnt: 交易几次,取值0~k次;
                         status: 交易状态,表示是否拥有股票。 0-没有股票,只能买;1-拥有1股,只能卖。
2、状态方程

第k次卖出: 从第k次买入后转换而来,或者是第k次卖出后保持不动
dp[day][cnt][0] = max(dp[day-1][cnt][0],dp[day-1][cnt-1][1]+prices[day])

第k次买入: 从第k-1次卖出转换而来,或者第k次买入后保持不动
dp[day][cnt-1][1] = max(dp[day-1][cnt-1][1],dp[day-1][cnt-1][0]-prices[day])


for day : 0-->n-1
  for cnt : 0-->k
  
      dp[day,cnt,0]:表示当天没有股票; 
      dp[day,cnt,1]:表示当天拥有一股。
      
      // 核心部分--状态方程
      dp[day,cnt,0] = max {
    
     dp[day-1, cnt, 0], dp[day-1, cnt-1, 1]+prices[day] }
      dp[day,cnt,1] = max {
    
     dp[day-1, cnt, 1], dp[day-1, cnt-1, 0]-prices[day] }

return dp[n-1, {
    
    0,...,k}, 0]:第n天且没有股票的时候,之前交易的最大值

代码:

class Solution {
    
    
    public int maxProfit(int k, int[] prices) {
    
    
        int len = prices.length;
        if (k >= len / 2) return quickSolve(prices);
        
 		// 定义二维数组,交易了多少次、当前的买卖状态
        int[][] dp = new int[k+1][2];
        int res = 0;
        for(int i=0;i<=k;++i) {
    
    
            dp[i][0] = 0;
            dp[i][1] = -prices[0]; 
        }
        for(int i=1;i<n;++i) {
    
    
            for(int j=k;j>0;--j) {
    
    
                //处理第k次买入
                dp[j-1][1] = Math.max(dp[j-1][1], dp[j-1][0]-prices[i]);
                //处理第k次卖出
                dp[j][0] = Math.max(dp[j][0], dp[j-1][1]+prices[i]);

            }
        }
        return dp[k][0];
    }
    
    private int quickSolve(int[] prices) {
    
    
        int len = prices.length, profit = 0;
        for (int i = 1; i < len; i++)
            // as long as there is a price gap, we gain a profit.
            if (prices[i] > prices[i - 1]) profit += prices[i] - prices[i - 1];
        return profit;
    }
}

时间复杂度: O ( k n ) O(kn) O(kn)
空间复杂度: O ( k ) O(k) O(k)

版本2

思路:

1、状态定义
dp[k, i]:第 i 天时,第 k 笔交易产生的最大利润。

2、状态方程
dp[k, i] = max(dp[k, i-1], prices[i] - prices[j] + dp[k-1, j-1]), j=[0..i-1]

简单解释下,可分为交易与不交易两种情况:
1)不交易:最大利润 profit = dp[k, i] = dp[k, i-1]2)交易:  最大利润 dp[k, i] = prices[i] - prices[j] + dp[k-1, i-1]= j-th天购买, i-th卖出。 
                          // 这里i==j亦成立,看起来是失去了一次交易的机会
                          = prices[i] - prices[j] + dp[k-1, i-1] = dp[k-1, i]

代码:

class Solution {
    
    
    public int maxProfit(int k, int[] prices) {
    
    
        int len = prices.length;
        if (k >= len / 2) return quickSolve(prices);
        
        int[][] dp = new int[k + 1][len];
        for (int i = 1; i <= k; i++) {
    
    
            int tmpMax = -prices[0];
            for (int j = 1; j < len; j++) {
    
    
                dp[i][j] = Math.max(dp[i][j - 1], prices[j] + tmpMax);
                tmpMax =  Math.max(tmpMax, dp[i - 1][j - 1] - prices[j]);
            }
        }
        return t[k][len - 1];
    }
    
    private int quickSolve(int[] prices) {
    
    
        int len = prices.length, profit = 0;
        for (int i = 1; i < len; i++)
            // as long as there is a price gap, we gain a profit.
            if (prices[i] > prices[i - 1]) profit += prices[i] - prices[i - 1];
        return profit;
    }
}

时间复杂度: O ( k n ) O(kn) O(kn)
空间复杂度: O ( k n ) O(kn) O(kn)

三、更多

1、前期概要

M[day][cnt][status]含义: day: 表示天,取值0~n-1; 
                         cnt: 交易几次,取值0~k次;
                         stock: 持有股数。 0-没有股票,只能买;1-拥有1股,只能卖。

for day : 0-->n-1
    for cnt : 0-->k

      MP[day,cnt,0]:表示当天没有股票; 
      MP[day,cnt,1]:表示当天拥有一股。
      
      MP[day,cnt,0] = max {
    
    
                        MP[day-1, cnt, 0]          // 不动 
                        MP[day-1, cnt-1, 1]+a[day] // 卖掉一股
                      }
    
      MP[day,cnt,1] = max {
    
    
                        MP[day-1,cnt,1]            // 不动
                        MP[day-1,cnt-1,0]-a[day]   // 买入一股
                      }

return MP[n-1, {
    
    0,...,k}, 0]:第n天且没有股票的时候,之前交易的最大值

2、进阶1

可以买1股,可以买2股,也可以买3股,任意多股,

M[day][cnt][status]含义: day: 表示天,取值0~n-1; 
                         cnt: 交易几次,取值0~k次;
                         stock: 持有股数。取值从{
    
    01}变为{
    
    012....,X}

for day : 0-->n-1
  for cnt : 0-->k
    MP[day,cnt,stock] = max {
    
    
                              MP[day-1, cnt, stock]               // 不动
                              MP[day-1, cnt-1, stock+1] + a[day]  // 卖掉一股
                              MP[day-1, cnt-1, stock-1] + a[day]  // 买入一股
                            }

return MP[n-1, {
    
    0,...,k}, 0]:第n天且没有股票的时候,之前交易的最大值

3、进阶2

竞赛难度—状态压缩、加速、树形DP:具体略。

四、参考

1、A Concise DP Solution in Java
2、四种解法+图解 188.买卖股票的最佳时机 IV
3、Detail explanation of DP solution

猜你喜欢

转载自blog.csdn.net/HeavenDan/article/details/108982770