LeetCode Best Time to Buy and Sell Stock 购买股票的最佳时机(题解合集、动态规划)

一笔交易,求最佳利润

题目
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。
注意你不能在买入股票前卖出股票。
示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。
示例 2:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
分析:只允许完成一笔交易,求利润的最大值,必须先买入再卖出。这题可以用动态规划求解。
状态:dp[i],表示已知前i天价格可以推出的最大利润。
状态转移方程:dp[i]=Math.max(dp[i-1], prices[i]-min);下一天的利润为,上一天的利润和当天价格减去之前最小价格得到的差值中的较大者。
算法实现

public class Solution{
	 public int maxProfit(int[] prices) {
	      if(prices.length==0)
	    	  return 0;
	      int[] dp=new int[prices.length];	      
	      int min=10000;
	      for(int i=1;i<prices.length;i++){
	    	  if(prices[i-1]<min)
	    		  min=prices[i-1];
	    	  dp[i]=Math.max(dp[i-1], prices[i]-min);
	      }
	      return dp[prices.length-1];	
	 }	
}

其他求解思路:使用一个HashMap保存数组中值的位置和值的大小。然后写一个两重for循环比对得到最大差值,即最大利润,条件是先买入再卖出,低价买入高价卖出。这种算法花费的时间大于动态规划。
算法实现

public class Solution2 {
    public int maxProfit(int[] prices) {
        HashMap<Integer, Integer> map=new HashMap<>();
        for(int i=0;i<prices.length;i++){
        	map.put(i,prices[i]);
        }
        int max=0;
        for(int i=0;i<prices.length;i++){
    	   for(int j=0;j<prices.length;j++){
	        	if(i>j&&map.get(i)-map.get(j)>max){
	        		max=prices[i]-prices[j];
	        	}
    	   }
        }
        return max;   
    }
}

允许多笔交易,求最佳利润

题目
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
示例 2:
输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
分析
题目中涉及到两个状态,一个是持有股票的状态,一个是未持有股票的状态,这类在题目中可以明显找到状态的就可以使用动态规划求解。用两个数组分别表示在不同天数下的两种状态的最大利润。
状态:持有股票的状态dp1[i]和未持有股票的状态dp2[i]
状态转移方程
dp1[i]=Math.max(dp1[i-1], dp2[i-1]-prices[i]);
dp2[i]=Math.max(dp2[i-1], dp1[i-1]+prices[i]);
第一个状态转移方程是求持有股票的状态的,下一天的最大利润可能有两种情况,保持上一天的利润,或者它是由当天买入得到的状态,那么就是dp2[i-1]减去价格。
第二个状态转移方程是求未持有股票的状态,下一天的利润可以是上一天的保持,也可以由当天卖出股票后的利润,自然是由dp1[i]推得。
初始化
dp1[0]=-prices[0];
特殊情况
if(prices.length==0)
return 0;
代码

class Solution {
    public int maxProfit(int[] prices) {
    	if(prices.length==0)
    		return 0;
         int[] dp1=new int[prices.length];//持有股票状态的利润
         int[] dp2=new int[prices.length];//未持有股票状态的利润
         dp1[0]=-prices[0];
         for (int i = 1; i < prices.length; i++) {
			dp1[i]=Math.max(dp1[i-1], dp2[i-1]-prices[i]);
			dp2[i]=Math.max(dp2[i-1], dp1[i-1]+prices[i]);
		}
         return Math.max(dp1[prices.length-1], dp2[prices.length-1]);
    }
}

卖出后有一天的冷冻期

题目
给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。​
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
示例:
输入: [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]
状态:持有股票dp1[i]和未持有股票dp2[i]。把冷冻期归结为未持有股票。
状态转移方程
dp1[i]=Math.max(dp1[i-1],(i>1?dp2[i-2]:0)-prices[i]);
dp2[i]=Math.max(dp2[i-1],dp1[i-1]+prices[i]);
和上一题的区别是这题要考虑冷冻期,那么当天买入的上一个状态是未持有股票的两天前的状态。
初始化
dp1[0]=-prices[0];//初始状态
特殊情况:输入数组长度为0需要返回0;i-2的地方要做i >1的判断。
代码

class Solution {
    public int maxProfit(int[] prices) {
    	 if(prices.length==0){
    		return 0;
    	 }
    	 //状态
         int[] dp1=new int[prices.length];//持有股票,买入
         int[] dp2=new int[prices.length];//未持有股票,卖出和冷冻期
         dp1[0]=-prices[0];//初始状态         
         for(int i=1;i<prices.length;i++){
        	 dp1[i]=Math.max(dp1[i-1],(i>1?dp2[i-2]:0)-prices[i]);
        	 dp2[i]=Math.max(dp2[i-1],dp1[i-1]+prices[i]);        	 
         }
         return Math.max(dp1[prices.length-1], dp2[prices.length-1]);
    }
}

最多两笔交易,求最佳利润

题目
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: [3,3,5,0,0,3,1,4]
输出: 6
解释: 在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。
随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。
示例 2:
输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:
输入: [7,6,4,3,1]
输出: 0
解释: 在这个情况下, 没有交易完成, 所以最大利润为 0。
分析:如果直接去讨论是非常复杂的,这题依然可以通过动态规划来求解,只是这题的状态是四个状态,买入1,卖出1,买入2,卖出2,当然也可能是只完成一笔交易。
状态:buy1[i],sell1[i],buy2[i],sell2[i]。分别表示在某天买入或者卖出股票时的最大利润。
状态转移方程:基本思路是先第一次买入,第一次买入的状态会影响到第一次卖出,第一次卖出的状态会影响到第二次买入,第二次买入的状态会影响到第二次卖出。
buy1[i]=Math.max(buy1[i-1],-prices[i]); 可能是上一天的buy1;也可能是当天买入,取利润大的。
sell1[i]=Math.max(sell1[i-1], buy1[i-1]+prices[i]);可能是上一天的买入;也可能是当天卖出,如果当天卖出,利润是上一天买入状态的利润加上当天的价格。
buy2[i]=Math.max(buy2[i-1], sell1[i-1]-prices[i]);可能是上一天的买入;也可能是当天买入,第一笔交易结束后得到该状态。
sell2[i]=Math.max(sell2[i-1], buy2[i-1]+prices[i]);可能是上一天的卖出;也可能是第二笔买入后得到该状态。
初始化
buy1[0]=-prices[0]; 刚开始可以买入
buy2[0]=-prices[0]; 所有buy都要初始化
特殊情况
if(prices.length<2)
return 0;
最大利润
Math.max(sell1[prices.length-1],sell2[prices.length-1]);
代码

public class Solution {
	    public int maxProfit(int[] prices) {
	    	if(prices.length<2)
	    		return 0;
	        int[] buy1=new int[prices.length];
	        int[] sell1=new int[prices.length];
	        int[] buy2=new int[prices.length];
	        int[] sell2=new int[prices.length];
	        buy1[0]=-prices[0];
	        buy2[0]=-prices[0];	        
	        for(int i=1;i<prices.length;i++){
	        	buy1[i]=Math.max(buy1[i-1],-prices[i]);
	        	sell1[i]=Math.max(sell1[i-1], buy1[i-1]+prices[i]);
	        	buy2[i]=Math.max(buy2[i-1], sell1[i-1]-prices[i]);
	        	sell2[i]=Math.max(sell2[i-1], buy2[i-1]+prices[i]);
	        }	        
	        return Math.max(sell1[prices.length-1],sell2[prices.length-1]);        
	    }	
}

最多k笔交易,求最大利润

题目
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。
注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: [2,4,1], k = 2
输出: 2
解释: 在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。
示例 2:
输入: [3,2,6,5,0,3], k = 2
输出: 7
解释: 在第 2 天 (股票价格 = 2) 的时候买入,在第 3 天 (股票价格 = 6) 的时候卖出, 这笔交易所能获得利润 = 6-2 = 4 。
随后,在第 5 天 (股票价格 = 0) 的时候买入,在第 6 天 (股票价格 = 3) 的时候卖出, 这笔交易所能获得利润 = 3-0 = 3 。
分析
这是在两笔交易的基础上的一种扩展,只要根据输入的k值来动态分配空间就可以了。
如何优化空间
按照之前的思路,我们是需要分配一个二维数组的空间来保存信息,二维数组的长度是k,宽度是prices数组的长度。占用的空间非常之大,这是很难通过后面的数值比较大的测试的,于是我们可以把二维降为一维,只要知道每笔交易的上一次的结果是多少就足够推导出下一次的情况了。数组长度选择为k,位置不同的下标表示不同的交易。
状态
买入和卖出两个状态,分配两个一维数组就可以了。
状态转移方程

           if(j==0){
			   in[j]=Math.max(in[j], -prices[i]);
			   out[j]=Math.max(out[j], in[j]+prices[i]);
		   }else{
			   in[j]=Math.max(in[j], out[j-1]-prices[i]);
			   out[j]=Math.max(out[j], in[j]+prices[i]);       			  
		   }

第一笔交易和别的交易略有不同,它的买入不涉及上一笔交易。在这里,体现了状态的更新,它会随着prices数组的遍历不断更新数据的。
特殊情况处理
开头对数组长度和k值做一些判断和约束(这个很重要)

if(prices.length<2||k==0)
        	   return 0;
if(k>prices.length/2){
   k=prices.length/2;
  }

完整代码

public class Solution {	
    public int maxProfit(int k, int[] prices) {
           if(prices.length<2||k==0)
        	   return 0;
           if(k>prices.length/2){
        	   k=prices.length/2;
           }
           int[] in=new int[k];
           int[] out=new int[k];
           for(int i=0;i<k;i++){
        	   in[i]=-prices[0];
           }
           for(int i=1;i<prices.length;i++){
        	   for(int j=0;j<k;j++){
        		   if(j==0){
        			   in[j]=Math.max(in[j], -prices[i]);
        			   out[j]=Math.max(out[j], in[j]+prices[i]);
        		   }else{
        			   in[j]=Math.max(in[j], out[j-1]-prices[i]);
        			   out[j]=Math.max(out[j], in[j]+prices[i]);       			  
        		   }
        	   }
           }
           return out[k-1];
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值