代码随想录算法训练营Day45|动态规划part08

110 阅读6分钟

LeetCode 121 买卖股票的最佳时机

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

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

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

思路

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

这道题我们用贪心算法做过,这次用动态规划分析。注意只能买入一次股票。

考虑动态规划五部曲:

  1. 确定dp数组及其下标含义:dp[i][0]表示到第i天(含第i天)持有股票手中的最大现金数,dp[i][1]表示到第i天(含第i天)不持有股票手中的最大现金数。
  2. 确定递推公式:
    • 如果第i天持有股票,那么要么第i-1天也持有,要么前i-1天不持有而第i天买入。所以dp[i][0] = max(dp[i-1][0], -prices[i])
    • 如果第i天不持有股票,那么要么第i-1天也不持有,要么第i-1天持有了但第i天卖出了股票。所以dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[i])
  3. dp数组如何初始化:dp[0][0]初始化为-prices[0]dp[0][1]初始化为0
  4. 确定遍历顺序:从小到大遍历
  5. 举例推导dp数组

解法

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

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

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

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

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

思路

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

在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。

返回 你能获得的 最大 利润 。

这次可以不限制次数地买卖股票,同样我们考虑动态规划五部曲:

  1. 确定dp数组及其下标含义:dp[i][0]表示到第i天(含第i天)持有股票手中的最大现金数,dp[i][1]表示到第i天(含第i天)不持有股票手中的最大现金数。
  2. 确定递推公式:
    • 如果第i天持有股票,那么要么第i-1天也持有,要么前i-1天不持有而第i天买入。所以dp[i][0] = max(dp[i-1][0], dp[i-1][1] - prices[i])
    • 如果第i天不持有股票,那么要么第i-1天也不持有,要么第i-1天持有了但第i天卖出了股票。所以dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[i])
  3. dp数组如何初始化:dp[0][0]初始化为-prices[0]dp[0][1]初始化为0
  4. 确定遍历顺序:从小到大遍历
  5. 举例推导dp数组

解法

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

LeetCode 123 买卖股票的最佳时机III

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

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

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

思路

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。

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

这次我们只能买卖两次股票,所以对于同一天有五种状态,未持有,第一次持有,第一次持有后卖出,第二次持有,第二次持有后卖出。所以dp数组的第二个维度有5个值。下面考虑动态规划五部曲:

  1. 确定dp数组及其下标含义:
    1. dp[i][0]表示到第i天(含第i天)从未持有股票手中的最大现金数
    2. dp[i][1]表示到第i天(含第i天)正第一次持有股票手中的最大现金数。
    3. dp[i][2]表示到第i天(含第i天)持有过一次且已卖出股票手中的最大现金数
    4. dp[i][3]表示到第i天(含第i天)正第二次持有股票手中的最大现金数
    5. dp[i][4]表示到第i天(含第i天)第二次持有后已卖出股票手中的最大现金数
  2. 确定递推公式:
    • 如果第i天从未持有股票,那么之前i-1天也从未持有。所以dp[i][0] = dp[i-1][0]
    • 如果第i天正第一次持有,那么要么第i-1天也正第一次持有,要么前i-1天不持有而第i天买入。所以dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i])
    • 如果第i天持有过一次且已卖出,那么要么第i-1天也持有过一次且已卖出,要么第i-1天正第一次持有而第i天卖出。所以dp[i][2] = max(dp[i-1][2], dp[i-1][1] + prices[i])
    • 如果第i天正第二次持有,那么要么第i-1天也正第二次持有,要么第i-1天持有过一次且已卖出而第i天买入。所以dp[i][3] = max(dp[i-1][3], dp[i-1][2] - prices[i])
    • 如果第i天第二次持有后已卖出,那么要么第i-1天也第二次持有后已卖出,要么第i-1天正第二次持有但第i天卖出。所以dp[i][4] = max(dp[i-1][4], dp[i-1][3] + prices[i])
  3. dp数组如何初始化:dp[0][0]初始化为0,dp[0][1]初始化为-prices[0]dp[0][2]初始化为0,dp[0][3]初始化为-prices[0]dp[0][4]初始化为0
  4. 确定遍历顺序:i从小到大遍历,j从0到4遍历
  5. 举例推导dp数组

解法

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

今日收获总结

今日学习一个小时,动态规划股票问题的关键是搞清楚每天有几种买卖股票的状态,在状态之间做递推。