代码随想录算法训练营Day46|动态规划part09

91 阅读1分钟

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

题目链接:leetcode.cn/problems/be…

文档讲解:programmercarl.com/0188.买卖股票的最…

视频讲解:www.bilibili.com/video/BV16M…

思路

给你一个整数数组 prices 和一个整数 k ,其中 prices[i] 是某支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。也就是说,你最多可以买 k 次,卖 k 次。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

本题就是123.买卖股票的最佳时机3️⃣的进阶版,123题相当于固定k=2.我们观察123题的递推公式,把第二维用j表示,当j不为0时可以总结出此递推公式

dp[i][j]=max(dp[i1][j],dp[i1][j1]+(1)jprices[i])dp[i][j] = max(dp[i-1][j], dp[i-1][j-1]+(-1)^jprices[i])

考虑动态规划五部曲:

  1. 确定dp数组及其下标含义:dp[i][j]表示在第i天已经操作(买入or卖出)j次股票,所拥有的最大现金数。j最大值为2k。
  2. 确定递推公式:dp[i][j]=max(dp[i1][j],dp[i1][j1]+(1)jprices[i])dp[i][j] = max(dp[i-1][j], dp[i-1][j-1]+(-1)^jprices[i])
  3. dp数组如何初始化:dp[0][j],如果j是偶数,初始化为0.如果j是奇数,初始化为- prices[0]
  4. 确定遍历顺序:外层遍历天数i,内层遍历j,都从小到大遍历
  5. 举例推导dp数组

[!info] 复杂度分析

  • 时间复杂度:O(nk)O(nk)
  • 空间复杂度:O(nk)O(nk)

优化空间

观察我们的递推公式,可以发现第i天的状态仅依赖前一天,所以可以删除维度i。

dp[j]=max(dp[j],dp[j1]+(1)jprices[i])dp[j] = max(dp[j], dp[j-1]+(-1)^jprices[i])

为了让dp[j-1]仍然是前一天的状态,我们需要对j从大到小遍历。这样可以降低空间复杂度为O(k)O(k)

解法

原始解法

class Solution {
	public int maxProfit(int k, int[] prices) {	
		int[][] dp = new int[prices.length][2 * k + 1];				  
		
		for (int j = 0; j <= 2*k; j++) {		
			if (j % 2 == 0) {			
				dp[0][j] = 0;				
			}			
			else {			
				dp[0][j] = -prices[0];			
			}		
		}		
		for (int i = 1; i < prices.length; i++) {			
			for (int j = 0; j <= 2*k; j++) {			
				if (j == 0) {				
					dp[i][j] = 0;				
				}				
				else {				
					dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-1] + (int)Math.pow(-1, j) * prices[i]);				
				}			
			}		
		}		
		return dp[prices.length-1][2*k];	
	}
}

优化空间

class Solution {
	public int maxProfit(int k, int[] prices) {	
		int[] dp = new int[2 * k + 1];				  
		
		for (int j = 0; j <= 2*k; j++) {		
			if (j % 2 == 0) {			
				dp[j] = 0;			
			}			
			else {			
				dp[j] = -prices[0];			
			}		
		}		
		for (int i = 1; i < prices.length; i++) {		
			for (int j = 2*k; j >= 0; j--) {			
				if (j == 0) {				
					dp[j] = 0;				
				}				
				else {				
					dp[j] = Math.max(dp[j], dp[j-1] + (int)Math.pow(-1, j) * prices[i]);				
				}			
			}		
		}		
		return dp[2*k];	
	}
}

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

题目链接:leetcode.cn/problems/be…

文档讲解:programmercarl.com/0309.最佳买卖股票…

视频讲解:www.bilibili.com/video/BV1rP…

思路

给定一个整数数组prices,其中第  prices[i] 表示第 i 天的股票价格 。​

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

  • 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

考虑在本情景下,有几种状态及他们之间的转化关系:

  1. 未持有股票,也不在冷冻期。前一天可以是状态1或4
  2. 持有股票。前一天可以是状态1或2或4
  3. 未持有股票,当天卖出了股票。前一天是状态2
  4. 未持有股票,但在冷冻期。前一天为状态3

考虑动态规划五部曲:

  1. 考虑dp数组及其下标含义:
    1. dp[i][0]:第i天未持有股票,也不在冷冻期,所能拥有的最大现金数
    2. dp[i][1]:第i天持有股票,所能拥有的最大现金数
    3. dp[i][2]:第i天未持有股票,当天卖出股票,所能拥有的最大现金数
    4. dp[i][3]:第i天未持有股票,但在冷冻期,所能拥有的最大现金数
  2. 确定递推公式:根据前文的状态转化关系
    1. dp[i][0] = max(dp[i-1][0], dp[i-1][3])
    2. dp[i][1] = max(dp[i-1][0] - prices[i], dp[i-1][1], dp[i-1][3] - prices[i])
    3. dp[i][2] = dp[i-1][1] + prices[i]
    4. dp[i][3] = dp[i-1][2]
  3. 确定初始化方式
    1. dp[0][0]:0
    2. dp[0][1]:-prices[0]
    3. dp[0][2]:0
    4. dp[0][3]:0
  4. 确定遍历顺序:i从小到大遍历
  5. 举例推导dp数组

解法

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

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

题目链接:leetcode.cn/problems/be…

文档讲解:programmercarl.com/0714.买卖股票的最…

视频讲解:www.bilibili.com/video/BV1z4…

思路

给定一个整数数组 prices,其中 prices[i]表示第 i 天的股票价格 ;整数 fee 代表了交易股票的手续费用。

你可以无限次地完成交易,但是你每笔交易都需要付手续费。如果你已经购买了一个股票,在卖出它之前你就不能再继续购买股票了。

返回获得利润的最大值。

注意:这里的一笔交易指买入持有并卖出股票的整个过程,每笔交易你只需要为支付一次手续费。

本情景中的状态并不复杂,只有两种,且它们相互转换

  1. 持有股票
  2. 未持有股票。由于一笔交易只要支付一次手续费,在卖出的时候需要减去一份手续费。

考虑动态规划五部曲:

  1. 考虑dp数组及其下标含义: 1. dp[0]表示在第i天持有股票,所能拥有的最大现金数 2. dp[1]表示在第i天不持有股票,所能拥有的最大现金数
  2. 确定递推公式
    1. dp[0] = max(dp[0], dp[1] - prices[i])
    2. dp[1] = max(dp[1], dp[0] + prices[i] - fee)
  3. 确定初始化方式
    1. dp[0] = - prices[0]
    2. dp[1] = 0
  4. 确定遍历顺序:对i从小到大遍历,为了让递推公式右边的数据都是前一天的,使用temp保留第i天的数据,计算完成后统一复制。
  5. 举例推导dp数组

解法

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

[!info] 复杂度分析

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

股票问题总结

股票问题.png

今日收获总结

今日学习三小时,对于有多个复杂状态的股票问题,除了要清晰的定义每个状态的含义,也要明确状态之间的转化关系。