股票系列问题总结

股票系列问题


121.只允许卖一次

Say you have an array for which the ith element is the price of a given stock on day i.
If you were only permitted to complete at most one transaction (i.e., buy one and sell one share of the stock), design an algorithm to find the maximum profit.
Note that you cannot sell a stock before you buy one.

思路很简单,因为只可以卖一次,我们便利数组统计在每个点卖出的最大收益,该值可以通过维护之前的最小买入价,然后和当前价格做差来计算。


122.可以卖无数次

Say you have an array for which the ith element is the price of a given stock on day i.
Design an algorithm to find the maximum profit. You may complete as many transactions as you like (i.e., buy one and sell one share of the stock multiple times).
Note: You may not engage in multiple transactions at the same time (i.e., you must sell the stock before you buy again).

实际上我们至多只能完成 len/2 次的操作,这个细节在本题无用,在后续会用到;

现在考虑一个情况, stocks = [1,3,5] , 题目里说到我们在每一天只能完成一次操作,对于这个数组很简单我们最大收益就是 5-1=4. 为了便于考虑,我们现作出如下决定 : 在每一天,如果前一天的价格比当前价格少,那么我们就完成一次交易来获得 a[i]a[i1] a [ i ] − a [ i − 1 ] 的收入。在这个例子里,会变成 0 买入, 1卖出, 1买入,2卖出。可以看出按照这个思路我们在 i==1 i == 1 完成了买入和卖出两次交易,看似违背了规则,但实际上我们相当于在这一天什么都没有做,注意理解这一点之后,我们的编码就很简单。

time:O(n) t i m e : O ( n )
space:O(1) s p a c e : O ( 1 )


123.至多卖两次

Say you have an array for which the ith element is the price of a given stock on day i.
Design an algorithm to find the maximum profit. You may complete at most two transactions.

采用分治的思想,我们完成两次交易,那我们必然可以找到一个时间节点,在这一点的两侧分别完成一次交易。那我们的思路就是基于只卖一次的思想,遍历每一个节点,计算该点前、后部分完成一次交易的最大利润并相加。
Naively 我们的思路就是两层循环, time:O(n2) t i m e : O ( n 2 )
进一步思考发现每一步信息重复率很大,存在不必要的时间浪费;
换一个思路,发现基于121的思路,通过left>right 维护一个min 值,可以统计出该点及之前一次买卖的最大收益;基于这个思路,我们right>left 遍历,维护一个max值,那么可以统计出该点及该点以后的最大收益;根据这两个收益就可以求出要的值。

class Solution {
    public int maxProfit(int[] a) {
        if(a.length==0) return 0;
        int[] fr =  new int[a.length];
        int[] back = new int[a.length];
        int min = a[0];
        for(int i = 1;i<a.length;i++){
            if(a[i]<=min){
                min = a[i];
                fr[i] = fr[i-1];
                continue;
            }
            fr[i] = Math.max(fr[i-1],a[i]-min);
        }
        int max = a[a.length-1];
        for(int i = a.length-2;i>-1;i--){
            if(a[i]>=max){
                max = a[i];
                back[i] = back[i+1];
                continue;
            }
            back[i] = Math.max(back[i+1],max-a[i]);
        }
        int res = back[0];
        \\注意这一步,因为前面构思里每一个点都是包含其中的,只能通过下面的代码进行遍历,这种遍历会丢失整个数组只卖一次的情况,所以初始值直接设置back[0]
        for(int i = 0;i<a.length-1;i++){
            res = Math.max(res,fr[i]+back[i+1]);
        }
        return res;
    }
}

time:O(n) t i m e : O ( n )
space:O(n) s p a c e : O ( n )


188.至多卖K次

Say you have an array for which the ith element is the price of a given stock on day i.
Design an algorithm to find the maximum profit. You may complete at most k transactions.

可以看做123的一般情况,因而这里的方法是可以用于123的;
假设 dp[i][k] d p [ i ] [ k ] 表示在第i天之前(包括i天)完成至多k次交易的情况下所能获得的最大收益;那么可以有递推公式:

dp[i][k]=max(dp[i1][k],dp[j][k1]+price[i]price[j](j<i)) d p [ i ] [ k ] = m a x ( d p [ i − 1 ] [ k ] , d p [ j ] [ k − 1 ] + p r i c e [ i ] − p r i c e [ j ] ( ∀ j < i ) )
前半段很好理解,就是当天不做任何操作,直接保留前一天完成k次操作的利润;
后半段可以分两种情况:

1.在j天没有卖股票,那么很好理解,j天完成了k-1次的值就是dp[j][k-1],并且我们第j天可以买入当天的股票然后在i天卖出, 总利润就是: dp[j][k1]+price[i]price[j] d p [ j ] [ k − 1 ] + p r i c e [ i ] − p r i c e [ j ]

2.在j天卖出了股票,理论上我们不能再进行任何操作,但回归到之前122的例子里,我们可以默认在j天卖出的同时买入,等同于没有进行任何操作,所以总利润依然是 dp[j][k1]+price[i]price[j] d p [ j ] [ k − 1 ] + p r i c e [ i ] − p r i c e [ j ]
(简单证明:
如果 dp[j][k-1] 是j天卖出股票的收益,那么我们想在j天继续持有j , 收益为: dp[j][k1]price[j] d p [ j ] [ k − 1 ] − p r i c e [ j ] ; 在这个情况下我们等到 i 天卖出,则又进账了price[i] , 总收益为: dp[j][k1]+price[i]price[j] d p [ j ] [ k − 1 ] + p r i c e [ i ] − p r i c e [ j ]
其实dp[j][k-1]可以理解为在j天所能拥有的最大资产,我们所做的只是把资产的一部分变成了等价格的股票,所以这里取到的依旧是最大的收益,只不过形式产生了变化,从dp[j][k-1]的钱,变成了 {dp[j][k-1] - a[j]的钱+一支以a[j]买入的股票} ,然后我们在i天卖出该股票就是将后半部分置换成了a[i]的钱,也就是前面的地推公式。)

理论上来说为了求得递推公式左边的最大值,我们需要遍历所有i天之前的值来进行计算,分析公式右边可以看出:

max(dp[j][k1]+price[i]price[j])=max(dp[j][k1]price[j])+price[i]; m a x ( d p [ j ] [ k − 1 ] + p r i c e [ i ] − p r i c e [ j ] ) = m a x ( d p [ j ] [ k − 1 ] − p r i c e [ j ] ) + p r i c e [ i ] ;

可以看出我们所需要的值只是前面所有天数中dp[j][k-1] - price[j]的最大值,因而这里我们通过维护这个值,将一次递推的复杂度降到了 O(N) O ( N ) ;

现在考虑空间复杂度,基于之前的想法,由于需要dp[i][k]的矩阵,则:
Space:O(nk) S p a c e : O ( n ∗ k ) ;
分析过程中可以看见,递推过程只会用到上一层的数据,则只需要dp[i]的数组;
递推过程中需要dp[i-1][k] 以及需要前面数据维护的常量,因而递推过程从小到大循环;

class Solution {
    public int maxProfit(int k, int[] a) {
        if(a.length<=0) return 0;
        //error2
        if(k>a.length/2){
            int res = 0;
            for(int i = 1;i<a.length;i++){
                if(a[i]>a[i-1]) res += a[i]-a[i-1];
            }
            return res;
        }
        int[] dp = new int[a.length];
        for(int x = 1;x<=k;x++){
            int cons = -a[0];
            //error1
            for(int i = 1;i<a.length;i++){
                int curcons = dp[i] - a[i];
                dp[i] = Math.max(dp[i-1],cons+a[i]);
                cons = Math.max(cons,curcons);
            }
        }
        return dp[a.length-1];
    }
}

time:O(nk) t i m e : O ( n ∗ k )
space:O(n) s p a c e : O ( n )

错误统计:
1.cons初始值设置为0.考虑递推公式,j==0时候

dp[j][k1]+a[i]a[j]=a[i]a[j] d p [ j ] [ k − 1 ] + a [ i ] − a [ j ] = a [ i ] − a [ j ]

所以cons应该设置成 -a[0] 。(宏观理解,这里常量cons记录的实际上是在这一点完成k-1次交易,并且不卖出该股票的值,0出无法完成交易,因此就是持有该股票亏损的钱 -a[0])

2.超时:
一个长为1000的数组,允许完成1000000次交易;
分析:时间复杂度是(n*k),这里k值过大导致超时。
由于我们一次交易至多只能做len/2次操作,所以当输入的k值>=len/2时候,这道题已经转变成了可以卖无数次的情况,使用122中 O(n)的解法就可以解决了。


309.冷却时间1天:

Say you have an array for which the ith element is the price of a given stock on day i.
Design an algorithm to find the maximum profit. You may complete as many transactions as you like (ie, buy one and sell one share of the stock multiple times) with the following restrictions:
You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again).
After you sell your stock, you cannot buy stock on next day. (ie, cooldown 1 day)

一开始沿用前面的思路,尝试着用dp的思路去分析做了出来。
设dp[i]是第i天允许卖出的情况下最大收益(不一定需要卖出)
先找递推公式,观察发现第i天能否买卖其实是由i-2和i-1共同决定的,i-2天如果卖出了,那么这里只能进行购入操作,相反如果i-2天没有卖出,那么需要考虑i-1,i-1如果卖出了那么这一天什么都不能做,反之这一天可以选择卖或者不卖。
现在假设在第i天卖出j天买入的股票,因为是j天买入,所以j-1天必然不能进行卖出,则这里抛出股票的最大收益就是:

sell[i]=dp[j2]+a[i]a[j](j<i) s e l l [ i ] = d p [ j − 2 ] + a [ i ] − a [ j ] ( ∀ j < i )
观察这个式子可以发现dp[j-2] - a[j]同样是一个可以在之前遍历过程中就确定最大值的常量。
那么这里我们也可以选择不卖出股票,则这一步的最大收益也可以是dp[i-1];
综上我们得到公式:
dp[i]=Math.max(dp[i1],max(dp[j2]a[j])+a[i](j<i)) d p [ i ] = M a t h . m a x ( d p [ i − 1 ] , m a x ( d p [ j − 2 ] − a [ j ] ) + a [ i ] ( j < i ) )
分析这个式子可以看出每一步需要的数据只是之前两步的最大收益,所以空间复杂度降低到 O(1) O ( 1 ) ;

class Solution {
    public int maxProfit(int[] a) {
        if(a.length<=1) return 0;
        int two = 0;
        //I-2的收益
        int one = Math.max(0,a[1]-a[0]);
        //I-1的收益;
        int cons = Math.max(-a[0],-a[1]);
        for(int i = 2;i<a.length;i++){
            int sell = cons+a[i];
            cons = Math.max(cons,two-a[i]);
            two = one;
            one = Math.max(sell,one);
        }
        return one;
    }
}

这一题通过绘制状态图分析可以以很简单的思路遍历所有可能性:

这里写图片描述
基于这个图,在每一个点我们可以考虑三种状态的抵达:
1.noStock : 可以是读cd进入,也可以是上一步没持有不动:
noStock=Math.max(noStock,cd) n o S t o c k = M a t h . m a x ( n o S t o c k , c d )
2.hold : 可以是上一步已经持有,也可以是上一步没持有这一步买入:
hold=Math.max(hold,noStocka[i] h o l d = M a t h . m a x ( h o l d , n o S t o c k − a [ i ]
3.cd : 将上一步持有的卖出
cd=hold+a[i] c d = h o l d + a [ i ]

思路很棒可是理解起来有点复杂,通过一个case画图可以发现,这里每一步相当于是用greedy的思想,来获得当前这个状态的最大利润,实际是在每一步判断了所有可能的情况。


714.每次交易需要费用:

首先还是dp的思路,和前面类似,每一步考虑卖或者不卖:

dp[i]=max(dp[j1]+a[i]a[j]fee![](https://imgblog.csdn.net/20180731234153772?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI0NDM2MzEx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)(j<i),dp[i1]) d p [ i ] = m a x ( d p [ j − 1 ] + a [ i ] − a [ j ] − f e e ! [ 这 里 写 图 片 描 述 ] ( h t t p s : / / i m g − b l o g . c s d n . n e t / 20180731234153772 ? w a t e r m a r k / 2 / t e x t / a H R 0 c H M 6 L y 9 i b G 9 n L m N z Z G 4 u b m V 0 L 3 F x X z I 0 N D M 2 M z E x / f o n t / 5 a 6 L 5 L 2 T / f o n t s i z e / 400 / f i l l / I 0 J B Q k F C M A == / d i s s o l v e / 70 ) ( ∀ j < i ) , d p [ i − 1 ] )

所以还是在遍历过程中维护一个最优的 dp[j-1] - a[j] ,而且这里只需要用到上一步的数据,所以只需要一个变量last来保存上一步的最大利润

class Solution {
    public int maxProfit(int[] a, int fee) {
        if(a.length<=1) return 0;
        int last = 0;
        int val = -a[0];
        for(int i = 1;i<a.length;i++){
            int sell = val + a[i] - fee;
            val = Math.max(val,last-a[i]);
            last = Math.max(last,sell);
        }
        return last;
    }
}

同理这道题也可以使用状态图来遍历所有的情况,且十分简单这里写图片描述

class Solution {
    public int maxProfit(int[] a, int fee) {
        if(a.length==0) return 0;
        int noStock = 0;
        int hold = -a[0];
        for(int i = 1;i<a.length;i++){
            hold = Math.max(hold,noStock-a[i]);
            noStock = Math.max(noStock,hold+a[i]-fee);
        }
        return Math.max(hold,noStock);
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值