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

67 阅读4分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

原文链接:blog.csdn.net/roufoo/arti…

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天完成,所得到的最优利润。

转化方程为:

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]。 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。 参考了 www.hrwhisper.me/leetcode-be… 这个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的改进版本。 参考了 www.hrwhisper.me/leetcode-be… 解法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;
    }
};