LintCode 393: Best Time to Buy and Sell Stock IV (买股票 DP经典题)

本文探讨了在给定股票价格序列和最多允许交易次数的情况下,如何计算最大可能利润的问题。介绍了四种不同的动态规划解法,包括标准DP、空间优化DP、二次时间复杂度DP及其改进版。每种解法都有详细的步骤说明和对应的代码实现。
  1. Best Time to Buy and Sell Stock IV

Given an array prices and the i-th element of it represents the price of a stock on the i-th day.

You may complete at most k transactions. What’s the maximum profit?

Example
Example 1:

Input: k = 2, prices = [4, 4, 6, 1, 1, 4, 2 ,5]
Output: 6
Explanation: Buy at 4 and sell at 6. Then buy at 1 and sell at 5. Your profit is 2 + 4 = 6.
Example 2:

Input: k = 1, prices = [3, 2, 1]
Output: 0
Explanation: No transaction.
Challenge
O(nk) time. n is the size of prices.

Notice
You may not engage in multiple transactions at the same time (i.e., you must sell the stock before you buy again).

解法1:DP。
思路参考自网上。
设两个DP数组,分别为global[n][k+1]和local[n][k+1]。
global[i][j]表示前i天完成了j笔交易,所得到的最优利润。注意第j笔交易不一定在第i天完成。
local[i][j]表示前i天完成了j笔交易,并且第j笔交易一定在第i天完成,所得到的最优利润。

转化方程为:

  1. global[i][j]=max(local[i][j],global[i-1][j]),
    其中global[i-1][j]表示前i-1天共完成j笔交易(第i天并无交易)的最优利润,local[i][j]表示前i天共完成j笔交易并且第j笔在第i天完成的最优利润。显然,两者中的最大值为global[i][j]。
  2. local[i][j]=max(global[i-1][j-1], local[i-1][j])+ prices[i] - prices[i - 1],
    因为根据local[i][j]的定义,第i天必须完成第j比交易,所以我们要考虑两种情况。一种是前i-1天已经做了j-1笔交易,
    其中global[i-1][j-1]表示前i-1天共完成j-1笔交易(第i天并无交易)的最优利润,local[i-1][j]表示前i-1天共完成j笔交易并且第j天完成第i-1笔交易的最优利润。光global[i-1][j-1]+diff还不行,因为global[i-1][j-1]这个全局最优没把第j天考虑进去,所以要再考虑local[i-1][j]+diff,这个就是把第j比交易在第i-1天完成换成在第i天完成。

注意:网上好多答案都用了global[i-1][j-1]+max(diff, 0)。diff=prices[i] - prices[i - 1]。我认为直接用diff就可以。因为如果diff<0,那么下面用global[i][j]=global[i-1][j]就可以了,因为prices[i-1]比prices[i]大。

注意:
1)当k >= n / 2时,没有必要用O(n^2)的DP算法,直接用O(n)的累加即可。此时该问题转换为无限次交易的买股票问题。参考LintCode 150: Best Time to Buy and Sell Stock II

class Solution {
public:
    /**
     * @param K: An integer
     * @param prices: An integer array
     * @return: Maximum profit
     */
    int maxProfit(int k, vector<int> &prices) {
        int n = prices.size();
        if (n == 0) return 0;
    
        if (k >= n / 2) {
            int result = 0;
            for (int i = 1; i < n; ++i)
                result += max(prices[i] - prices[i - 1], 0);
            return result;
        }
        
        vector<vector<int>> global(n, vector<int>(k + 1, 0));
        vector<vector<int>> local(n, vector<int>(k + 1, 0));
        
        for (int i = 1; i < n; ++i) {
            //for (int j = 1; j <= k; ++j) { // is also OK
            for (int j = k; j >= 1; --j) {
               local[i][j] = max(global[i - 1][j - 1], local[i - 1][j]) +  prices[i] - prices[i - 1];
               global[i][j] = max(global[i - 1][j], local[i][j]);
            }
        }
        return global[n - 1][k];
    }
};

解法2:解法1的空间简化版本,类似01背包的空间优化。
代码如下:

class Solution {
public:
    /**
     * @param K: An integer
     * @param prices: An integer array
     * @return: Maximum profit
     */
    int maxProfit(int k, vector<int> &prices) {
        int n = prices.size();
        if (n == 0) return 0;
    
        if (k >= n / 2) {
            int result = 0;
            for (int i = 1; i < n; ++i) {
                if (prices[i] - prices[i - 1] > 0)   
                    result += prices[i] - prices[i - 1];
            }
        
            return result;
        }
        
        vector<int> global(k + 1);
        vector<int> local(k + 1);
        
        for (int i = 1; i < n; ++i) {
            for (int j = k; j >= 1; --j) {
               local[j] = max(global[j - 1], local[j]) + prices[i] - prices[i - 1];;
               global[j] = max(global[j], local[j]);
            }
        }
        return global[k];
    }
};

解法3:DP。
参考了 https://www.hrwhisper.me/leetcode-best-time-to-buy-and-sell-stock-i-ii-iii-iv/
这个DP是最容易想到的,中规中矩的一种DP。
dp[i][j]表示第i天完成了第j次交易,即该dp[][]相当于解法1/2中的local[][]。//注意,下面为了代码方便用dp[j][i]表示。
状态转移方程:
dp[i][x] = max(dp[i-1][x] , dp[j][x – 1] + prices[i] – prices[j]) 0 <= j < i
dp[i-1][x]为第i天不进行交易,dp[j][x – 1] + prices[i] – prices[j]为枚举j从0~i-1,第j天买入,第i天卖出。
该算法时间复杂度为O(n^2*k),当输入规模大的时候会超时。

代码如下:

class Solution {
public:
    /**
     * @param K: An integer
     * @param prices: An integer array
     * @return: Maximum profit
     */
    int maxProfit(int k, vector<int> &prices) {
        int n = prices.size();
        if (n == 0) return 0;
        int result = 0;
     
        if (k >= n / 2) {
            for (int i = 1; i < n; ++i)
                result += max(prices[i] - prices[i - 1], 0);
            return result;
        }
        
        vector<vector<int>> dp(n, vector<int>(k + 1, 0));
        
        for (int i = 1; i <= k; ++i) {
            for (int j = 1; j < n; ++j) {
                dp[j][i] = dp[j - 1][i]; //initial value is no transaction on day j
                for (int x = 0; x < j; ++x) {
                    dp[j][i] = max(dp[j][i], dp[x][i - 1] + prices[j] - prices[x]);
                }
            }
            result = max(result, dp[n - 1][i]); 
        }
        return result;
    }
};

解法4:解法3的改进版本。
参考了 https://www.hrwhisper.me/leetcode-best-time-to-buy-and-sell-stock-i-ii-iii-iv/
解法3的每一个for x循环就是为了得到每次固定的i,j从1…n-1遍历所取的dp[j][i-1]-prices[j]的最大值。
那我们就可以直接用一个变量maxTemp来记录dp[j][i-1]-prices[j]对每个i所取的最大值。
另外,应该也可以做预处理记录这个数据。应该两层循环一个一维数组就够了。
时间复杂度改进到O(nk)。

class Solution {
public:
    /**
     * @param K: An integer
     * @param prices: An integer array
     * @return: Maximum profit
     */
    int maxProfit(int k, vector<int> &prices) {
        int n = prices.size();
        if (n == 0) return 0;
        int result = 0;
     
        if (k >= n / 2) {
            for (int i = 1; i < n; ++i)
                result += max(prices[i] - prices[i - 1], 0);
            return result;
        }
        
        vector<vector<int>> dp(n, vector<int>(k + 1, 0));
        
        for (int i = 1; i <= k; ++i) {
            //maxTmp records the maximum vlue of dp[j][i - 1] - prices[j], for fixed i, j=1..n-1
            int maxTmp = -prices[0];  //dp[0][i - 1]-prices[0], j=0
            for (int j = 1; j < n; ++j) {
                dp[j][i] = max(dp[j - 1][i], maxTmp + prices[j]);
                maxTmp = max(maxTmp, dp[j][i - 1] - prices[j]);
            }
            result = max(result, dp[n - 1][i]); 
        }
        return result;
    }
};
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值