动态规划4:买卖股票类问题

本期题目

  • 最佳观光组合
  • 买卖股票的最佳时机
  • 买卖股票的最佳时机II
  • 最佳买卖股票时机含冷冻期
  • 买卖股票的最佳时机含手续费

1014. 最佳观光组合

这道题目表达成优化问题是 max ⁡ 0 ≤ i < j ≤ n − 1 v a l u e [ i ] + v a l u e [ j ] + i − j \max_{0\leq i<j\leq n-1} value[i]+value[j]+i-j 0i<jn1maxvalue[i]+value[j]+ij我们可以对固定的i,先找到最大的j,然后找最大的i即可。给定 i = 0 , 1 , ⋯   , n − 2 i=0,1,\cdots,n-2 i=0,1,,n2,求解优化问题: max ⁡ j = i + 1 , ⋯   , n − 1 ( v a l u e [ i ] + v a l u e [ j ] + i − j ) \max_{j=i+1,\cdots,n-1}(value[i]+value[j]+i-j) j=i+1,,n1max(value[i]+value[j]+ij)由于在给定i的情况下 v a l u e [ i ] + i value[i]+i value[i]+i是常数,所以 max ⁡ j = i + 1 , ⋯   , n − 1 ( v a l u e [ i ] + v a l u e [ j ] + i − j ) = v a l u e [ i ] + i + max ⁡ j = i + 1 , ⋯   , n − 1 ( v a l u e [ j ] − j ) \begin{aligned} &\max_{j=i+1,\cdots,n-1}(value[i]+value[j]+i-j)\\\\=&value[i]+i+\max_{j=i+1,\cdots,n-1}(value[j]-j) \end{aligned} =j=i+1,,n1max(value[i]+value[j]+ij)value[i]+i+j=i+1,,n1max(value[j]j)所以,我们要求的就是 max ⁡ j = i + 1 , ⋯   , n − 1 ( v a l u e [ j ] − j ) \max_{j=i+1,\cdots,n-1}(value[j]-j) maxj=i+1,,n1(value[j]j),设 d p [ i ] = max ⁡ j = i + 1 , ⋯   , n − 1 ( v a l u e [ j ] − j ) dp[i]=\max_{j=i+1,\cdots,n-1}(value[j]-j) dp[i]=maxj=i+1,,n1(value[j]j),那么就有 d p [ i ] = max ⁡ ( v a l u e [ i ] − i , d p [ i + 1 ] ) 0 ≤ i ≤ n − 2 dp[i]=\max(value[i]-i,dp[i+1])\quad 0\leq i\leq n-2 dp[i]=max(value[i]i,dp[i+1])0in2
所以,本题还是一个动态规划问题,只不过动态规划算法用在了一个转化问题上。

int maxScoreSightseeingPair(const vector<int>& value){
  	int n=value.size();
 	 if(n==1) return 0;
 	 else if(n==2) return (value[0]+value[1]-1);
 	 else{
	    int dp=value[n-1]-n+1;
	    int maxScore=value[n-2]+value[n-1]-1;
	    for(int i=n-2;i>=0;i--){
		      maxScore=max(maxScore,value[i]+i+dp);
		      dp=max(dp,value[i]-i);
	    }
	    return maxScore;
  	}
}

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

121. 买卖股票的最佳时机

稍加思考就不难发现,这道题就是变形版的最大价值,写成优化问题就是 max ⁡ ( max ⁡ 0 ≤ i < j ≤ n − 1 ( p r i c e s [ j ] − p r i c e s [ i ] ) , 0 ) \max(\max_{0\leq i<j\leq n-1} (prices[j]-prices[i]),0) max(0i<jn1max(prices[j]prices[i]),0)
同样地,要求解 max ⁡ 0 ≤ i < j ≤ n − 1 ( p r i c e s [ j ] − p r i c e s [ i ] ) \max_{0\leq i<j\leq n-1} (prices[j]-prices[i]) max0i<jn1(prices[j]prices[i]),我们可以化为 max ⁡ 0 ≤ i < j ≤ n − 1 ( p r i c e s [ j ] − p r i c e s [ i ] ) = max ⁡ 0 ≤ i ≤ n − 2 max ⁡ i + 1 ≤ j ≤ n − 1 ( p r i c e s [ j ] − p r i c e s [ i ] ) = max ⁡ 0 ≤ i ≤ n − 2 [ − p r i c e s [ i ] + max ⁡ i + 1 ≤ j ≤ n − 1 ( p r i c e s [ j ] ) ] \begin{aligned} &\max_{0\leq i<j\leq n-1} (prices[j]-prices[i])\\ =&\max_{0\leq i\leq n-2}\max_{i+1\leq j\leq n-1}(prices[j]-prices[i])\\ =&\max_{0\leq i\leq n-2}[-prices[i]+\max_{i+1\leq j\leq n-1}(prices[j])] \end{aligned} ==0i<jn1max(prices[j]prices[i])0in2maxi+1jn1max(prices[j]prices[i])0in2max[prices[i]+i+1jn1max(prices[j])]因此,这里的动态规划时用来求prices数组i+1到n-1的最大值。

int maxProfit(const vector<int>& prices){
	  int n=prices.size();
	  if(n<=1) return 0;
	  else{
		    int maxprice = prices[n-1];
		    int maxprofit = 0;
		    for(int i=n-2;i>=0;i--){
			      maxprofit=max(maxprofit,maxprice-prices[i]);
			      maxprice=max(maxprice,prices[i]);
		    }
		    return maxprofit;
	  }
}

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

实际上, − p r i c e s [ i ] + max ⁡ i + 1 ≤ j ≤ n − 1 ( p r i c e s [ j ] ) -prices[i]+\max_{i+1\leq j\leq n-1}(prices[j]) prices[i]+maxi+1jn1(prices[j])的含义是选择在 i i i时刻买入所能获得的最大收益,而选择在 i i i时刻买入,需要付出的成本是 − p r i c e s [ i ] -prices[i] prices[i],之后要选择某一天卖出,则最优选择当然是在价格最高点处卖出,所以可以获得的最大收益是 max ⁡ i + 1 ≤ j ≤ n − 1 ( p r i c e s [ j ] ) \max_{i+1\leq j\leq n-1}(prices[j]) maxi+1jn1(prices[j]),我们可以看到上面的算法还是非常直观的。

122. 买卖股票的最佳时机II

现在,我们可以参与多笔交易,那么问题就很难写成一个优化问题的形式,我们需要重新定义一下状态空间。

对于某一天 i i i,我们可以选择买入或卖出,或者不进行任何操作。如果我们手头上没有持有任何的股票,我们可以选择买入股票或者不进行任何的操作。如果我们手头上持有了股票,我们只能选择卖出或者继续持有股票等待后面卖出。对于最后一天,如果我们手头上持有了股票,那么只能选择卖出以获得最大利润,如果我们手头上没有股票,那么什么都不能做。

我们定义状态为持有股票和不持有股票,行为定义为买、卖和不行动,那么,状态转移可以用以下图表示:

买卖股票最佳时机II

每一条边都表示采取行动可能会导致的状态转移,状态转移会产生单步收益 R ( s i − 1 , s i , a i − 1 ) R(s_{i-1},s_i,a_{i-1}) R(si1,si,ai1),可以写在边上作为边的权重,现在我们需要找一条从头到尾的路径,使得边的权重之和最大化。

我们可以定义一个价值函数 V i ( s i ) V_i(s_{i}) Vi(si),表示在 i i i时刻处于状态 s i s_i si可以获得的最大收益,那么就有如下动态规划转移方程 V i − 1 ( s i − 1 ) = max ⁡ a i − 1 ( R ( s i − 1 , s i , a i − 1 ) + V i ( s i ) ) V_{i-1}(s_{i-1})=\max_{a_{i-1}}(R(s_{i-1},s_i,a_{i-1})+V_i(s_i)) Vi1(si1)=ai1max(R(si1,si,ai1)+Vi(si))其中 s i s_i si为状态为 s i − 1 s_{i-1} si1时采取行动 a i − 1 a_{i-1} ai1会转移到的状态。这就是动态规划递归方程,因为这里面收益涉及到状态的转移,我们也可以叫作状态转移方程。我们后面都称为状态转移方程。

int maxProfit(const vector<int>& prices){
	  int n=prices.size();
	  if(n<=1) return 0;
	  else{
		    int hasStockValue=prices[n-1];
		    int Not_hasStockValue=0;
		    for(int i=n-2;i>=1;i--){
			      int tmp1=hasStockValue;
			      int tmp2=Not_hasStockValue;
			      hasStockValue=max(tmp1,prices[i]+Not_hasStockValue);
			      Not_hasStockValue=max(tmp2,-prices[i]+hasStockValue);
		    }
		    return max(-prices[0]+hasStockValue,Not_hasStockValue);
	  }
}

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

如果要找到最佳决策序列,则需要准备两个数组,然后从0时刻到最后一个时刻进行回溯,具体代码略。

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

在解决了最佳买卖股票时机II之后,我们有了一个解决这类问题的一个思考框架,就是定义时间步、状态空间、行动,每一步都有各自的状态空间和行动空间,每一步采取行动会产生状态转移,从而产生相应的收益,这个收益是状态转移的函数,然后再求一个最优的状态转移路径,使得总收益最大化,实际上,最佳买卖股票实际可以视为如下状态转移图:
买卖股票最佳时机I

因此,也可以据此设计出相应的动态规划算法进行求解。

在309题中,我们多了一个冷冻状态,在冷冻状态中,我们不持有股票,但是也不能进行买的操作,现在,我们多了一个状态,但是方法是不变的。现在的状态转移图如下:
最佳股票买卖时机含冷冻期

从状态转移图可以看出冷冻状态只能转移到下一时间点的不持有状态,所以其价值函数就是下一时期不持有状态的价值函数,但是为了方便还是加入这么一个冷冻状态。

int maxProfit(const vector<int>& prices){
  int n=prices.size();
  if(n<=1) return 0;
  else{
    int has=prices[n-1];
    int freeze=0;
    int nothas=0;
    for(int i=n-2;i>=1;i--){
      int tmp1=has;
      int tmp2=freeze;
      int tmp3=nothas;
      has=max(tmp1,prices[i]+tmp2);
      freeze=tmp3;
      nothas=max(tmp3,-prices[i]+tmp1);
    }
    return max(-prices[0]+has,nothas);
  }
}

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

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

这个问题只是在买卖股票最佳时机II的基础上改变了边的权重而已,这里不再作进一步的分析,直接给出代码,我们这里假定买的操作需要付出手续费。

int maxProfit(const vector<int>& prices,int fee){
	  int n=prices.size();
	  if(n<=1) return 0;
	  else{
		    int hasStockValue=prices[n-1];
		    int Not_hasStockValue=0;
		    for(int i=n-2;i>=1;i--){
			      int tmp1=hasStockValue;
			      int tmp2=Not_hasStockValue;
			      hasStockValue=max(tmp1,prices[i]+Not_hasStockValue);
			      Not_hasStockValue=max(tmp2,-prices[i]-fee+hasStockValue);
		    }
		    return max(-prices[0]+hasStockValue,Not_hasStockValue);
	  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值