Best Time to Buy and Sell Stock I / II / III / IV / with cooldown / with Transaction Fee买卖股票6题

121.Best Time to Buy and Sell Stock I

题意:用一个数组表示股票每天的价格,数组的第i个数表示股票在第i天的价格。 如果只允许进行一次交易,也就是说只允许买一支股票并卖掉,求最大的收益。

分析:动态规划法。从前向后遍历数组,记录当前出现过的最低价格,作为买入价格,并计算以当天价格出售的收益,作为可能的最大收益,整个遍历过程中,出现过的最大收益就是所求。

复杂度时间O(n),空间O(1)。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int res = 0, cur_min = INT_MAX;
        for (int i = 0; i < prices.size(); i++) {
            cur_min = min(cur_min, prices[i]);
            res = max(res, prices[i] - cur_min);
        }
        return res;
    }
};

 

122.Best Time to Buy and Sell Stock II

题目:用一个数组表示股票每天的价格,数组的第i个数表示股票在第i天的价格。交易次数不限,但一次只能交易一支股票,也就是说手上最多只能持有一支股票,求最大收益。

复杂度时间O(n),空间O(1)。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        // version 1: 贪心法。从前向后遍历数组,只要当天的价格高于前一天的价格,就算入收益。
        /*
        int res = 0; 
        for (int i = 1; i < prices.size(); i++) {
            res += prices[i] > prices[i - 1] ? prices[i] - prices[i - 1] : 0;
        }
        return res;
        */
        // version 2: buy: buy state, sell: sell state 动态规划一天买入状态的最大收益有两种情况,之前就是买入状态或者今天刚买入,取最大值,卖出状态同理。
        if (prices.empty()) {
            return 0;
        }
        int buy = -prices[0], sell = 0;
        for (int i = 0; i < prices.size(); i++) {
            buy = max(sell - prices[i], buy);
            sell = max(buy + prices[i], sell);
        }
        return sell;

    }
};

上面这两种1次交易和无限次(n)次交易其实都是下面k次交易的特例

 

123.Best Time to Buy and Sell Stock III

题意:用一个数组表示股票每天的价格,数组的第i个数表示股票在第i天的价格。最多交易两次,手上最多只能持有一支股票,求最大收益。

分析1:动态规划法。以第i天为分界线,计算第i天之前进行一次交易的最大收益preProfit[i],和第i天之后进行一次交易的最大收益postProfit[i],最后遍历一遍,max{preProfit[i] + postProfit[i]} (0≤i≤n-1)就是最大收益。第i天之前和第i天之后进行一次的最大收益求法同Best Time to Buy and Sell Stock I。

复杂度:时间O(n),空间O(n)。

// version 1
        /*
        if (prices.size() < 2) {
            return 0;
        }
        vector<int> left(prices.size(), 0);
        vector<int> right(prices.size(), 0);
        int min_ = prices[0], max_ = prices[prices.size() - 1], res = 0;
        for (int i = 1; i < prices.size(); i++) {
            min_ = min(min_, prices[i]);
            left[i] = max(prices[i] - min_, left[i - 1]);
        }
        for (int i = prices.size() - 2; i >= 0; i--) {
            max_ = max(max_, prices[i]);
            right [i] = max(max_ - prices[i], right[i + 1]);
        }
        for (int i = 1; i < prices.size(); i++) {
            res = max(res, left[i] + right[i]);
        }
        return res;

分析2:由之前的两个状态变成4个状态Buy1[i]表示前i天做第一笔交易买入状态最大收益;Sell1[i]表示前i天做第一笔交易卖出状态组大收益;Buy2[i] Sell2[i]同理;那么Sell2[i]=max{Sell2[i-1],Buy2[i-1]+prices[i]};Buy2[i]=max{Buy2[i-1],Sell[i-1]-prices[i]};Sell1[i]=max{Sell[i-1],Buy1[i-1]+prices[i]}; Buy1[i]=max{Buy[i-1],-prices[i]}。空间也可以优化成4个变量。

复杂度:时间O(n),空间O(1)。

  //version 2
        if (prices.size() < 2) {
            return 0;
        }
        int buy1 = INT_MIN, buy2 = INT_MIN, sell1 = 0, sell2 = 0;
        for (int i = 0; i < prices.size(); i++) {
            buy1 = max(buy1, -prices[i]);
            sell1 = max(sell1, buy1 + prices[i]);
            buy2 = max(buy2, sell1 - prices[i]);
            sell2 = max(sell2, buy2 + prices[i]);
        }
        return sell2;

 

分析3:下面的问题4的当k=2的特例。分析看下面k的情况

复杂度:时间O(n),空间O(1)。

代码们:

class Solution {
public:
    /*
     * @param prices: Given an integer array
     * @return: Maximum profit
     */
    int maxProfit(vector<int> &prices) {
        // write your code here
     
        // version 3
       if (prices.size() < 2) {
            return 0;
        }
        vector<int> global(3, 0);
        vector<int> local(3, 0);
        for (int i = 1; i < prices.size(); i++) {
            int diff = prices[i] - prices[i - 1];
            for (int k = 2; k >= 1; k--) {
                local[k] = max(global[k - 1], local[k]) + diff;
                global[k] = max(global[k], local[k]);
            }
        }
        return global[2];
        
    }
};

 

188.Best Time to Buy and Sell Stock IV

题意:用一个数组表示股票每天的价格,数组的第i个数表示股票在第i天的价格。最多交易k次,手上最多只能持有一支股票,求最大收益。

分析1:我们维护两种量,一个是当前到达第i天可以最多进行j次交易,最好的利润是多少(global[i][j]),另一个是当前到达第i天,最多可进行j次交易,并且最后一次交易在当天卖出的最好的利润是多少(local[i][j])。下面我们来看递推式,全局的比较简单,

global[i][j]=max(local[i][j],global[i-1][j]),

也就是去当前局部最好的,和过往全局最好的中大的那个(因为最后一次交易如果包含当前天一定在局部最好的里面,否则一定在过往全局最优的里面)。对于局部变量的维护,递推式是

local[i][j]=max(global[i-1][j-1]+max(diff,0),local[i-1][j]+diff),

也就是看两个量,第一个是全局到i-1天进行j-1次交易,然后加上今天的交易,如果今天是赚钱的话(也就是前面只要j-1次交易,最后一次交易取当前天),第二个量则是取local第i-1天j次交易,然后加上今天的差值(这里因为local[i-1][j]比如包含第i-1天卖出的交易,所以现在变成第i天卖出,并不会增加交易次数,而且这里无论diff是不是大于0都一定要加上,因为否则就不满足local[i][j]必须在最后一天卖出的条件了)。上面的算法中对于天数需要一次扫描,而每次要对交易次数进行递推式求解,所以时间复杂度是O(n*k),但是注意当K大于天数的时候,这时候就变成了可以进行无穷多次交易了,此时还用dp就会超时,应该用股票类问题2中的贪心算法。还有一点,在dp空间优化成1维的时候,用一维数组来代替二维数组,可以极大的节省了空间,由于覆盖的顺序关系,我们需要j从2到1,这样可以取到正确的g[j-1]值,而非已经被覆盖过的值

其实上述的递推公式关于local[i][j]的可以稍稍化简一下,我们之前定义的local[i][j]为在到达第i天时最多可进行j次交易并且最后一次交易在最后一天卖出的最大利润,第 i 天卖第 j 支股票的话,一定是下面的一种:

1. 今天刚买的
那么 Local(i, j) = Global(i-1, j-1)
相当于啥都没干

2. 昨天之前卖掉的,昨天刚买的
那么 Local(i, j) = Global(i-1, j-1) + diff
等于Global(i-1, j-1) 中的交易,加上今天干的那一票

3. 更早之前买的
那么 Local(i, j) = Local(i-1, j) + diff
昨天别卖了,留到今天卖

但其实第一种情况是不需要考虑的,因为当天买当天卖不会增加利润,完全是重复操作,这种情况可以归纳在global[i-1][j-1]中,所以我们就不需要max(0, diff)了,那么由于两项都加上了diff,所以我们可以把diff抽到max的外面,所以更新后的递推公式为:

local[i][j] = max(global[i - 1][j - 1], local[i - 1][j]) + diff

global[i][j] = max(local[i][j], global[i - 1][j])

 

给出代码:

int maxProfit(int K, vector<int> &prices) {
        // write your code here
        if (prices.size() < 2 || K < 1) {
            return 0;
        }
        if (K >= prices.size()) return solveMaxProfit(prices);
        vector<int> local(K + 1, 0);
        vector<int> global(K + 1, 0);
        for (int i = 1; i < prices.size(); i++) {
            int diff = prices[i] - prices[i - 1];
            for (int j = K; j >= 1; j--) {
                local[j] = max(global[j - 1] + max(diff, 0), local[j] + diff);
                global[j] = max(global[j], local[j]);
            }
        }
        return global[K];
    }
    int solveMaxProfit(vector<int> &prices) {
        int res = 0;
        for (int i = 1; i < prices.size(); ++i) {
            if (prices[i] - prices[i - 1] > 0) {
                res += prices[i] - prices[i - 1];
            }
        }
        return res;
    }

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

 

分析2:跟上面问题3的第二种思路的扩展,把2扩展成k即可

vector<int> buy(K + 1, INT_MIN);
        vector<int> sell(K + 1, 0);
        for (int i = 0; i < prices.size(); i++) {
            for (int j = 1; j < K + 1; j++) {
                buy[j] = max(buy[j], sell[j - 1] - prices[i]);
                sell[j] = max(sell[j], buy[j] + prices[i]);
            }
        }
        return sell[K];

不过同样记得判断当K很大时的情况啊。

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

 

309.Best Time to Buy and Sell Stock with cooldown 

 

 

而这道题与上面这些不同之处在于加入了一个冷冻期Cooldown之说,就是如果某天卖了股票,那么第二天不能买股票,有一天的冷冻期。此题需要维护三个一维数组buy, sell,和rest。其中:

buy[i]表示在第i天之前最后一个操作是买,此时的最大收益。

sell[i]表示在第i天之前最后一个操作是卖,此时的最大收益。

rest[i]表示在第i天之前最后一个操作是冷冻期,此时的最大收益。

我们写出递推式为:

buy[i]  = max(rest[i-1] - price, buy[i-1]) 
sell[i] = max(buy[i-1] + price, sell[i-1])
rest[i] = max(sell[i-1], buy[i-1], rest[i-1])


上述递推式很好的表示了在买之前有冷冻期,买之前要卖掉之前的股票。一个小技巧是如何保证[buy, rest, buy]的情况不会出现,这是由于buy[i] <= rest[i], 即rest[i] = max(sell[i-1], rest[i-1]),这保证了[buy, rest, buy]不会出现。

另外,由于冷冻期的存在,我们可以得出rest[i] = sell[i-1],这样,我们可以将上面三个递推式精简到两个,其实也可以直接理解为就是只能卖了隔一天才能买,所以递推买的时候要用两天前的数据。

buy[i]  = max(sell[i-2] - price, buy[i-1]) 
sell[i] = max(buy[i-1] + price, sell[i-1])

 

我们还可以做进一步优化,由于i只依赖于i-1和i-2,所以我们可以在O(1)的空间复杂度完成算法

代码:

int maxProfit(vector<int>& prices) {
        if (prices.size() < 2) {
            return 0;
        }
        int buy = -prices[0], sell = 0, sell_cache = 0;
        for (int i = 1; i < prices.size(); i++) {
            buy = max(buy, sell_cache - prices[i]);
            sell_cache = sell;
            sell = max(sell, buy + prices[i]);
        }
        return sell;

 

714.Best Time to Buy and Sell Stock with Transaction fee 

 

 

给定一组某一stock在每一天的价格,买卖次数不限,每次买入必须在卖出之后,且每次卖出时都需要fee的手续费,求解最大的收益。

 

buy[i] 表示第i天的买收益的最大

sell[i] 表示第i天的卖收益的最大

接着填写初时状态

buy[0] = - prices[0] ( 我们买了第一件)

sell[0] = 0 (没法卖,没有收益)

接着是状态转移

buy[i] = max( sell[i-1] - prices[i], buy[i-1]);  
( 前一天卖了后今天买了, 前一天的最大)

sell[i] = max( buy[i-1] + prices[i] - fee , sell[i-1]);

 

 

总结:

几种转移方程的思路:

第i天买入状态和第i天卖出状态
第i次买和第i次卖
第i天完成交易局部最优(第i天卖出)和第i天完成交易全局最优。(只有k次交易限制的时候用这个)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值