LeetCode 股票问题买股票最佳时机ⅠⅡⅢⅣ 含手续费含冷冻期

LeetCode No.121 买股票的最佳时机 && 面试题63.股票的最大利润

方法一:暴力解决法:
找到数组中最小的数以及该数之后的最大数即可。
最后返回它们的差值。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        int maxprofit = 0;
        for (int i = 0; i < n; ++i){
            for (int j = i + 1; j < n; ++j){
                maxprofit = max(maxprofit, prices[j] - prices[i]);
            }
        }
        return maxprofit;
    }
};

时间复杂度O(n²);
空间复杂度O(1);

LeetCode No.121 执行用时1536ms。

看到这个执行用时时我也惊呆了,然后又提交了一次:1756ms (lll¬ω¬)。


方法二:一次遍历法:
由于只买卖一支股票,所以并不需要建立其他的数组进行比较,思想同上,即找到最小的数以及该数之和的最大数即可,争取用一次遍历就得出结果。
由于事先并不知道数组里的各个数字属性,所以不知道最小的是有多小,也不知道最大的是有多大。那就设minprice = INT_MAX;maxprofit = 0;
代码如下:

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

时间复杂度O(n);
空间复杂度O(1);

LeetCode No.121 执行用时8ms。


LeetCode No.122 买股票的最佳时机Ⅱ

这道题在121题的基础上只增加了购买股票的权限次数,即可以尽可能多的购买股票。
所以联想到“贪心算法”(即在当下来说做出最好的选择
最好的选择在本题中的含义就是:只要盈利,即prices[j]-prices[i]>0就是最好的选择
所以把每段可以盈利的利润加起来就是买股票的最大利益。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int i=0int j=1;
        int sum=0;
        if(prices.size()<=1) return 0;
        while(j<prices.size()){
            if(prices[j]-prices[i]>0)
                sum+=(prices[j]-prices[i]);
                i++;
                j++;
        }
        return sum;
    }
};

时间复杂度O(n);
空间复杂度O(1);


LeetCode No.123 买股票的最佳时机Ⅲ

相对于第二题来说,这道题只允许最多买卖两次股票,并且要求求得最大的利润值。
大致思路依旧是贪心算法,但是是买卖交易两次小支的股票盈利多还是买一次大支的股票盈利多,这是无法知道的。
所以我们需要正向贪心算一次,反向再贪心算一次,通过比较正反之和→→与←←一次正向的数值求得最大值。后者的方法同No.121的一次遍历法。
方法一:一维vector

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        vector<int> vec1(n),vec2(n);
        if( n == 0) return 0;
        int minprice=prices[0];
        for(int i=1;i<n;i++){
            vec	1[i]=max(vec1[i-1],prices[i]-minprice);
            minprice=min(prices[i],minprice);
        }
        int maxprice=prices[n-1];
        for(int i=n-2;i>=0;i--){
            vec2[i]=max(vec2[i+1],maxprice-prices[i]);
            maxprice=max(maxprice,prices[i]);
        }
        int maxprofit = 0;
        for(int i=0;i<n-1;i++){
            maxprofit=max(maxprofit,max(vec1[i]+vec2[i+1],vec1[i]));
        }
        maxprofit=max(maxprofit,vec1[n-1]);
        return maxprofit;
    }
};

时间复杂度O(n);
空间复杂度O(n);

LeetCode No.123 执行用时12ms。


方法二:二维定长数组法
vector可以换成固定长度的二维数组,用以节省它自动生长所浪费的时间。

数组的结构:
array[天数][状态]
天数:0~prices.size()
状态0:没有持有股票。
状态1:现在持有股票。


基本的状态转移方程:

  • array[i][0] = max(arr[i-1][0], arr[i-1][1] + prices[i]);

我没有股票:
说法1:我很保守的,一直都没买进,所以没有持有股票。
说法2:我只是现在没有,其实我是今天才卖掉的!!!

  • array[i][1] = max(arr[i-1][1], arr[i-1][0] - prices[i]);

我持有股票:
说法1:我一直都有,只是没有找到合适的机会卖出。
说法2:我以前都没有股票,你看到的是我今天才买的。

初始化情况:

  • arr[0][0] = 0;第一天没有股票,也不能买卖股票,所以利润等于0;
  • arr[0][1] = -1;第一天没有股票,竟然持有股票?!-1表示不可能;

具体代码如下:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        if(n == 0) return 0;
        int arr[3][2];
        arr[0][0] = 0;
        arr[0][1] =-1;
        for(int i = 0; i<n;i++){
            for(int day = 2; day > 0; day--){
                if(i == 0){
                    arr[day][0] = 0;
                    arr[day][1] = -prices[i];
                    continue;
                }
                arr[day][0] = max(arr[day][0], arr[day][1] + prices[i]);
                arr[day][1] = max(arr[day-1][0] - prices[i], arr[day][1]);
            }
        }
        return arr[2][0];
    }
};

LeetCode No.123 执行用时4ms。


LeetCode No.714 买卖股票的最佳时机含手续费

应用No.123的二维数组法即可。
当然,这道题相对简单一些,用一维数组arr[2]就足够了。

arr[状态]

状态0:没有持有股票。
状态1:现在持有股票。
arr[0]可理解为:

  • 没有持有股票的状态
  • 当前拥有的现金数量
  • 当下所有的利润总和
    ——持有的现金也就算做利润。
    ——提示 :可以看下一题以更好地理解。

状态转移方程:
arr

  • arr[0] = max(arr[0],arr[1] + prices[i]);

我没有股票:
说法1:我很保守的,一直都没买进,所以没有持有股票。
说法2:我只是现在没有,其实我是今天才卖掉的!!!

  • arr[1] = max(arr[1],arr[0] - prices[i] - fee);假定买入股票时要收取手续费。

我持有股票:
说法1:我一直都有,只是没有找到合适的机会卖出。
说法2:我以前都没有股票,你看到的是我今天才买的。

初始化情况:

  • arr[0] = 0;第一天没有股票,也不能买卖股票,所以利润等于0;
  • arr[1] = -1;第一天没有股票,竟然持有股票?!-1表示不可能;
class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        int n = prices.size();
        if (n < 2) return 0;
        int arr [2];
        arr[0] = 0;
        arr[1] = -prices[0] -fee;
        for (int i = 1; i < n; i++){
            arr[0] = max(arr[0], arr[1] + prices[i]);
            arr[1] = max(arr[1], arr[0] - prices[i] - fee);
        }return arr[0];
    }
};

LeetCode No.714 执行时间92 ms, 在所有 C++ 提交中击败了98.95%的用户


LeetCode No.309 最佳买卖股票时机含冷冻期

先上代码:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        if (n < 2) return 0;
        int arr [3];

        arr[0] = 0;
        arr[1] = INT_MIN;
        arr[2] = 0;

        int precash = arr[0];
        int preshares = arr[1];

        for(int i = 0 ;i<n ; i++){
            arr[0] = max(precash,preshares+prices[i]);
            arr[1] = max(preshares,arr[2]-prices[i]);
            arr[2] = precash;
            
            precash = arr[0];
            prehold = arr[1];
        }
        return precash;
    }
};

LeetCode No.309 执行时间8 ms。

为了更好地理解arr[0],代码里设置了中间变量:
precash代表自己的现金。
preshares代表自己现持有的股份。
再结合arr[0] = max(precash,preshares+prices[i]);是保守没买还是刚刚卖出;
以及arr[1] = max(preshares,arr[2]-prices[i]);是刚刚买进还是一直未卖出;
这两句状态转移方程进行串通,这次明白了么?
明白的话就给出下面的优化后的代码:
(省略掉precashpreshares)

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int sold = 0, shares = INT_MIN, rest = 0;
        for (int price : prices) {
            int pre_sold = sold;
            sold = shares + price;
            shares = max(shares, rest - price);
            rest = max(rest, pre_sold);
        }
        return max(sold, rest);
    }
};

相对于没有优化地代码来说,这一段代码最后缺少一次max的比较,所以最后要加上
return max(sold,rest);

LeetCode No.309 执行时间8 ms。


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

第四题应该是股票问题中最难的题目了。也就是把难点都综合在一起了。
相对于其他题来说,它把交易次数设置为了变量k,入手点就是这个k
第一个问题kprices.size()之间的关系的是怎样的呢?
一次交易需要一天时间的买入和另一天时间的卖出,至少需要两天的时间。
所以 k 应该不超过prices.size()的一半,倘若k大于prices.size()的一半,那么这道题是不是就和上面咱们做过的LeetCode No.122 买股票的最佳时机Ⅱ是一样的了呢?k也就失去了题目中的意义了。
所以,到这里我们把LeetCode No.122 买股票的最佳时机Ⅱ的代码搬过来,与此同时就完成一半的工作了!
进而思考另一半的工作,在这里我比较喜欢应用三维vector方法。
arr[t交易次数][i天数][股份持有状态]
arr[k][prices.size+1][0/1]
虽然是三维的,但是其实和二维的差不多。
也就是把以上的代码总结到一起就是这一题的答案了。

基本的状态转移方程:

  • arr[t][i][0] = max(arr[t][i - 1][0], arr[t][i - 1][1] + prices[i - 1]);

我没有股票:
说法1:我很保守的,一直都没买进,所以没有持有股票。
说法2:我只是现在没有,其实我是今天才卖掉的!!!

  • arr[t][i][1] = max(arr[t][i - 1][1], arr[t - 1][i - 1][0] - prices[i - 1]);

我持有股票:
说法1:我一直都有,只是没有找到合适的机会卖出。
说法2:我以前都没有股票,你看到的是我今天才买的。

初始化情况:

  • arr[t][0][1] = INT_MIN; 第一天是无法进行买卖交易的,因为股票交易至少需要两天或两天以上,所以把第一天的持有股份设为INT_MIN
  • arr[0][i][1] = INT_MIN;无论什么时候,只要标志着没有交易成功过的,那么就设它为INT_MIN
class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        if(!prices.size()) return 0;
        if(k >= prices.size()/2)
        {
            int p=0,q=1,sum=0;
            if(prices.size()<=1) return 0;
            while(q<prices.size()){
            if(prices[q]-prices[p]>0)
                sum+=(prices[q]-prices[p]);
                p++;
                q++;
        }
        return sum;
        }

        vector<vector<vector<int>>> arr(k + 1, vector<vector<int>>(prices.size() + 1, vector<int>(2, 0)));
        for(int t = 0; t <= k; t++)
            arr[t][0][1] = INT_MIN;
        for(int i = 0; i < prices.size(); i++)
            arr[0][i][1] = INT_MIN;

        for(int t = 1; t <= k; t++)
        {
            for(int i = 1; i <= prices.size(); i++)
            {
                arr[t][i][0] = max(arr[t][i - 1][0], arr[t][i - 1][1] + prices[i - 1]);
                arr[t][i][1] = max(arr[t][i - 1][1], arr[t - 1][i - 1][0] - prices[i - 1]);
            }
        }
        return arr[k][prices.size()][0];
    }
};

LeetCode No.188 执行时间40 ms


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值