[leetcode] BestTimeSellStock I, II, III, IV

BestTimeSellStock I

  • 问题描述:给定一组数字,代表每天股票的价格。假定现在只能进行一笔交易,计算出所能获得利润的最大值。
  • 解法:针对每个价格,我们只要知道它前面价格的最小值即好。所有我们可以遍历整个数组,并用一个数字代表之前的所有数字的最小值。所以针对数组里面的每个数,我们都能获得一个如果在该点卖出的最大利润值。我们计算这些利润值里面的最大值即可。
  • 时间复杂度:O(N), 空间复杂度:O(1)
  • 代码:
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if(prices.empty())
            return 0;
        int low_price = prices[0];
        int profit = 0;
        for(int i=1;i<prices.size();i++){
            profit = max(profit, prices[i] - low_price);
            if(low_price > prices[i])
                low_price = prices[i];
        }
        return profit;
    }
};

BestTimeSellStock II

  • 问题描述:不同于I,你现在可以进行多笔交易。不过只能是buy-sell-buy-sell这样交替进行,不支持buy-buy的操作。
  • 分析:因为我们可以支持多笔交易,所以针对每个价格i,如果前一个价格低于该价格,我们就可以买入&卖出。这样就可以赚差价啦。
    • 我们可能会想如果后面有更高的价格呢?比如说0, 5, 100. 我们在5的时候赚了5,但是在100的时候,我们可以赚95,所有和还是100。相当于我们在0买入,在100卖出。
  • 时间复杂度:O(N), 空间复杂度:O(1)
  • 代码
int maxProfitV3(vector<int>& prices){
        int res = 0;
        int size = (int) prices.size();
        for(int i=1;i<size;i++){
            if(prices[i]>prices[i-1]){
                res += (prices[i]-prices[i-1]);
            }
        }
        return res;
    }

BestTimeSellStock III

  • 问题描述:不同于I和II,在本题中,我们最多只能完成两笔交易。不过只能是buy-sell-buy-sell这样交替进行,不支持buy-buy的操作。
  • 分析:
    • 我们先回顾一下只能进行一笔交易的情况
      • 我们用dp[i]表示在区间[0, i]卖出进行一次交易所能获得的最大利润。
      • dp[i] = max(dp[i-1], prices[i] - min(prices[j])) j ∈ [ 0 , i − 1 ] j\in[0, i-1] j[0,i1]
    • 那现在我们可以进行两笔交易
      • 假设我们已经得到了第一笔交易的dp[0~n]
      • 那么我们如何计算第二笔交易的呢?
      • 同样我们新建一个dp2[i],表示第2次在区间[0, i]卖出进行两次所能获得的最大利润。
      • dp2[i] = max(dp2[i-1], prices[i] - prices[j] + dp1[j] for j in [0, i-1])
      • 上面这个式子是个O(N^2)的复杂度,如果我们直接实现这个DP,会TLE,那么我怎么优化呢?
      • dp2[i] = max(dp2[i-1], prices[i] + max(dp1[j] - prices[j]) for j in [0, i-1])
      • 我们知道我们i是从0开始遍历的,所以我们可以维护一个变量tmpMax,代表就是当前的max(dp1[j] - prices[j]) for j in [0, i-1]。在每个price计算完成后,我们再更新tmpMax = max(tmpMax, dp1[i-1] - prices[i-1]);
    • 上面我们已经完成了最多两笔交易的情况,那么如果题目扩展一下,最多有K笔交易呢?这也就是题目BestTimeSellStock IV
      • 通过上面的分析,我们已经知道最多进行两次交易如何计算最大值了,那么怎么扩展到K次呢?
      • 我们假设dp[k][i]表示第k次,在[0~i]内交易所能获得的最大值。
      • dp[k][i] = max{dp[k][i-1], prices[i] - prices[j] + dp[k-1][j] for j in range(0, i-1)}
  • 代码:
int maxProfitV3(vector<int>& prices){
        int res = 0;
        int size = (int) prices.size();
        if(size == 0)
            return 0;
        int K = 2;
        int dp[K + 1][size + 1];
        memset(dp, 0, sizeof(dp));
        // dp[k][i] = max(dp[k][i-1], prices[i] - prices[j] + dp[k-1][j] for j ~[0, i-1])
        // dp[k][i] = max(dp[k][i-1], prices[i] + dp[k-1][j] - prices[j] for j ~[0, i-1])
        // dp[k][i] = max(dp[k][i-1], prices[i] + max(dp[k-1][j] - prices[j]))
        for(int k=1;k<=K;k++){
            int tmpMax = dp[k-1][0] - prices[0];
            for(int i=2;i<=size;i++){
                dp[k][i] = max(dp[k][i-1], prices[i-1] + tmpMax);
                tmpMax = max(tmpMax, dp[k-1][i-1] - prices[i-1]);
                res = max(dp[k][i], res);
            }
        }
        return res;
    }
  • 分析:上面代码的时间复杂度是O(KN), 空间复杂度是O(KN),那么针对空间复杂度我们可以进一步优化。
    • 在进行第k轮计算时,当我们计算第dp[k][i]的时候,我们只会用到前一个的dp[k-1][i-1]。
    • 所以我们可以新建一个变量tmp,每次保存的就是前一轮的dp[k-1][i-1]。
    • 每次更新tmp,tmp1=dp[i], update dp[i], tmp = tmp1
    • 代码:
int maxProfitV4(vector<int>& prices){
        int res = 0;
        int size = (int) prices.size();
        if(size == 0)
            return 0;
        int K = 2;
        int dp[size + 1];
        memset(dp, 0, sizeof(dp));
        // dp[k][i] = max(dp[k][i-1], prices[i] - prices[j] + dp[k-1][j-1] for j ~[0, i-1])
        // dp[k][i] = max(dp[k][i-1], prices[i] + dp[k-1][j-1] - prices[j] for j ~[0, i-1])
        // dp[k][i] = max(dp[k][i-1], prices[i] + max(dp[k-1][j-1] - prices[j]))
        for(int k=1;k<=K;k++){
            int tmpMax = dp[0] - prices[0];
            int saved_last = dp[1];
            for(int i=2;i<=size;i++){
                int tmp = dp[i];
                dp[i] = max(dp[i-1], prices[i-1] + tmpMax);
                tmpMax = max(tmpMax, saved_last - prices[i-1]);
                saved_last = tmp;
                res = max(dp[i], res);
            }
        }
        return dp[size];
    }
  • 分析:时间复杂度是O(KN), 空间复杂度我们已经降低到了O(N)。如果将上述代码提交到IV,还是会TLE,那么我们分析一下TLE的原因发现当我们K很大的时候,会进行很多不必要的操作。因为我们一共就N个数字,所以最多K也应该小于等于N/2。所以当我们发现K>=N/2的时候,就表明我们可以在所有的数字上进行操作,这就转化成了II的问题,那就是O(N)的复杂度。
  • 代码:
int maxProfitV4(int& k, vector<int>& prices){
        int len = prices.size();
        if (len<2) return 0;
        if (k>len/2){ // simple case
            int ans = 0;
            for (int i=1; i<len; ++i){
                ans += max(prices[i] - prices[i-1],0);
            }
            return ans;
        }
        int res = 0;
        int size = (int) prices.size();
        if(size == 0)
            return 0;
        int K = k;
        int dp[size + 1];
        memset(dp, 0, sizeof(dp));
        // dp[k][i] = max(dp[k][i-1], prices[i] - prices[j] + dp[k-1][j-1] for j ~[0, i-1])
        // dp[k][i] = max(dp[k][i-1], prices[i] + dp[k-1][j-1] - prices[j] for j ~[0, i-1])
        // dp[k][i] = max(dp[k][i-1], prices[i] + max(dp[k-1][j-1] - prices[j]))
        for(int k=1;k<=K;k++){
            int tmpMax = dp[0] - prices[0];
            int saved_last = dp[1];
            for(int i=2;i<=size;i++){
                int tmp = dp[i];
                dp[i] = max(dp[i-1], prices[i-1] + tmpMax);
                tmpMax = max(tmpMax, saved_last - prices[i-1]);
                saved_last = tmp;
                res = max(dp[i], res);
            }
        }
        return dp[size];
    }

BestTimeSellStock with Cooldown

  • 问题描述:和BestTimeSellStock II相似,没有交易次数的限制,但是我们在sell后必须经过一天的cooldown,不能进行操作。
  • 分析:
    • 假设说没有cooldown,那么我们可以的得到dp[i] = max(dp[i-1], prices[i] - prices[j] + dp[j])。
    • 但是现在我们有了一次cooldown,说明我们完成一次交易后不能立马进行下一次交易。
    • 为了方便理解,我们更改一下上面所用的dp[i],表示在第i个节点买入所能获得的最大利润,那么dp[i] = max(prices[j] - prices[i] + dp[j+2], dp[j]) for j in [i+1, n]
    • 所以我们可以得到如下的代码:
int maxProfit(vector<int>& prices) {
        int size = (int) prices.size();
        if(size == 0)
            return 0;
        int profits[size]; // 代表从第i点买入所能获得的收益
        memset(profits, 0, sizeof(profits));
        int res = 0;
        for(int i=size-1;i>=0;i--){
            int profit = 0;
            for(int j=i+1;j<size;j++){
                // 某时刻卖出
                if((j+2) < size)
                    profit = max(prices[j] - prices[i] + profits[j+2], max(profit, profits[j]));
                else
                    profit = max(prices[j] - prices[i], max(profit, profits[j]));

            }

            profits[i] = profit;
            res = max(res, profit);
        }
        return res;
    }
  • 分析:上述代码的时间复杂度是O(N^2),空间复杂度是O(N)。如何进行优化呢?我们发现如果没有cooldown的时候,我们可以一直交易。但是有了cooldown了,说明我们不能每时每刻都交易了,只有在有些时候可以。那么我们可以引出三个状态state_reset(S0), state_buy(S1), state_sell(S2). 关系如下所示.
    在这里插入图片描述
  • 那我们可以得到他们呢之间的关系:
    • S0[i] = max(S0[i-1], S2[i-1])
    • S1[i] = max(S0[i-1]-prices[i], S1[i-1]) // 由S0买入
    • S2[i] = S1[i-1] + prices[i]
  • 所以我们可以得到一个O(N)的代码:
int maxProfitV2(vector<int>& prices){
        int size = (int) prices.size();

        if(size == 0)
            return 0;
        int state_reset[size];
        int state_buy[size];
        int state_sell[size];
        memset(state_reset, 0, sizeof(state_reset));
        memset(state_buy, 0, sizeof(state_buy));
        memset(state_sell, 0, sizeof(state_sell));
        state_reset[0] = 0;
        state_buy[0] = -prices[0];
        state_sell[0] = -0x7FFFFFFF;
        for(int i=1;i<size;i++){
            state_reset[i] = max(state_reset[i-1], state_sell[i-1]); // 维持上一个状态,即不买入,或者是上一个卖出的状态的最大值
            state_buy[i] = max(state_reset[i-1] - prices[i], state_buy[i-1]);   // reset状态过来,或者说不买入也不卖出
            state_sell[i] = state_buy[i-1] + prices[i]; // 卖出
        }
        return max(state_sell[size-1], max(state_reset[size-1], state_buy[size-1]));
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值