LeetCode ---- 买卖股票系列问题思路与题解
Posted whc__
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode ---- 买卖股票系列问题思路与题解相关的知识,希望对你有一定的参考价值。
股票问题
121. 买卖股票的最佳时机
(只能买卖一次)
贪心法
/**
* 思路: 贪心法
* 1. 左边选择买入最小值
* 2. 循环遍历每个元素当成当天卖出,与买入最小值相减,并与结果利润最大值进行比较
*
* 时间: O(n)
* 空间: O(1)
*/
public int maxProfit(int[] prices) {
int leftMin = Integer.MAX_VALUE;
int res = 0;
for (int i = 0; i < prices.length; i++) {
if(prices[i] < leftMin) {
leftMin = prices[i];
}
res = Math.max(res, prices[i] - leftMin);
}
return res;
}
动态规划
/**
* 思路: 动态规划
* (一次股票买卖问题)
* 1. 确定dp数组以及下标含义
* dp[i][j] : 表示第i天处于j状态的最大值利润值 (j值只有0和1, 0代表持有股票(即买入), 1代表不持有股票(即卖出))
* 2. 确定递推公式
* dp[i][0] = Math.max(dp[i-1][0], -prices[i])
* 第i-1天就持有股票保持现状; 第i天买入股票
* dp[i][1] = Math.max(dp[i-1][1], prices[i] + dp[i-1][0])
* 第i-1天就不持有股票保持现状; 第i天卖出股票 + 第i-1天持有股票的最大值(即买入的最小值,在dp数组中,持有股票是按负数计算的)
* 3. 初始化
* 从上面的递推公式可以看出,初始状态有两个,dp[0][0]和dp[0][1]
* dp[0][0] = -prices[0]
* dp[0][1] = 0
* 4. 确定遍历顺序
* 从前往后遍历
*
* 5. 举例推导dp数组
*
* 时间: O(n)
* 空间: O(n)
*/
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 < prices.length; i++) {
// 持有股票
dp[i][0] = Math.max(dp[i-1][0], -prices[i]);
// 不持有股票
dp[i][1] = Math.max(dp[i-1][1], prices[i] + dp[i-1][0]);
}
return dp[prices.length-1][1];
}
动态规划优化
// 在前面动态规划的基础进行空间优化,从二维降为一维
public int maxProfit(int[] prices) {
int[] dp = new int[2];
dp[0] = -prices[0];
dp[1] = 0;
for (int i = 1; i < prices.length; i++) {
// 持有股票
dp[0] = Math.max(dp[0], -prices[i]);
// 不持有股票
dp[1] = Math.max(dp[1], prices[i] + dp[0]);
}
return dp[1];
}
122. 买卖股票的最佳时机II
(可以买卖多次)
贪心法
/**
* 思路: 贪心法
*
* 贪心策略:
* 局部最优:收集每天的正利润
* 全局最优:求得最大利润
*
* 将最终利润进行拆解
* 比如prices[3] - prices[0]
* (prices[3] - prices[2]) + (prices[2] - prices[1]) + (prices[1] - prices[0])
* 像这样利润分解为每天为单位的维度,不是从第0天到第3天整体考虑
* 由此推出 (prices[i] - prices[i-1]) + (prices[i-1] - prices[i-2]) + .....(prices[1] - prices[0])
*
* 举例: 7 1 5 10 3 6 4
* -6 4 5 -7 3 -2
* 第一天不纳入考虑,因为第一天没有利润
*
* 时间: O(n)
* 空间: O(1)
*/
public int maxProfit(int[] prices) {
int result = 0;
for (int i = 1; i < prices.length; i++) {
if(prices[i] - prices[i-1] > 0) {
result += prices[i] - prices[i-1];
}
}
return result;
}
动态规划
/**
* 思路: 动态规划
*
* 1. 确定dp数组以及下标含义
* dp[i][j]:表示第i天状态为j的时候,多次买卖后的最大利润值
* 2. 确定递推公式
*
* 持有股票
* dp[i][0] = Math.max(dp[i-1][0], -prices[i] + dp[i-1][1])
* 第i-1天持有股票保持现状 第i天买入股票 + 第i-1天不持有股票(与一次买卖不同的地方,因为这里存在多次买卖,所以要加上第i-1天不持有股票的利润)
*
* 不持有股票
* dp[i][1] = Math.max(dp[i-1][1], prices[i] + dp[i-1][0]
* 第i-1天不持有股票保持现状 第i天卖出股票 + 第i-1天持有股票的最大值(即买入的最小值,在dp数组中,持有股票是按负数计算的)
* 3. 初始化
* dp[0][0] = -prices[0]
* dp[0][1] = 0
* 4. 遍历顺序
* 从前往后遍历
*
* 时间: O(n)
* 空间: O(n)
*/
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 < prices.length; i++) {
// 持有股票,与一次买卖唯一不同的地方的递推公式上
dp[i][0] = Math.max(dp[i-1][0], -prices[i] + dp[i-1][1]);
// 不持有股票
dp[i][1] = Math.max(dp[i-1][1], prices[i] + dp[i-1][0]);
}
return dp[prices.length-1][1];
}
动态规划优化
// 动态规划 + 空间优化降为一维
// 时间: O(n)
// 空间: O(1)
public int maxProfit(int[] prices) {
int[] dp = new int[2];
dp[0] = -prices[0];
dp[1] = 0;
for (int i = 1; i < prices.length; i++) {
// 持有股票,与一次买卖唯一不同的地方的递推公式上
dp[0] = Math.max(dp[0], -prices[i] + dp[1]);
// 不持有股票
dp[1] = Math.max(dp[1], prices[i] + dp[0]);
}
return dp[1];
}
123. 买卖股票的最佳时机III
(最多买卖两次)
动态规划
/**
* 思路: 动态规划
* 1. 确定dp数组以及下标含义
* dp[i][j] : 表示第i天状态为j时,所获得的最大利润
* j的范围是0~4, dp[i][0]表示不操作, dp[i][1]表示第一次买入, dp[i][2]表示第一次卖出, dp[i][3]表示第二次买入, dp[i][4]表示第二次卖出
* 2. 确定递推公式
* dp[i][1] = Math.max(dp[i-1][1], -prices[i] + dp[i-1][0]);
* dp[i][2] = Math.max(dp[i-1][2], prices[i] + dp[i-1][1]);
* dp[i][3] = Math.max(dp[i-1][3], -prices[i] + dp[i-1][2]);
* dp[i][4] = Math.max(dp[i-1][4], prices[i] + dp[i-1][3]);
* 3. 初始化
* dp[0][0] = 0
* dp[0][1] = -prices[0]
* dp[0][2] = 0
* dp[0][3] = -prices[0]
* 4. 遍历顺序
* 从前往后遍历
*
* 时间: O(n)
* 空间: O(n)
*/
public int maxProfit(int[] prices) {
int[][] dp = new int[prices.length][5];
dp[0][1] = -prices[0];
dp[0][3] = -prices[0];
for (int i = 1; i < prices.length; i++) {
dp[i][1] = Math.max(dp[i-1][1], -prices[i] + dp[i-1][0]);
dp[i][2] = Math.max(dp[i-1][2], prices[i] + dp[i-1][1]);
dp[i][3] = Math.max(dp[i-1][3], -prices[i] + dp[i-1][2]);
dp[i][4] = Math.max(dp[i-1][4], prices[i] + dp[i-1][3]);
}
return dp[prices.length-1][4];
}
动态规划优化
// 动态规划 + 优化空间
// 时间: O(n) 空间: O(1)
public int maxProfit(int[] prices) {
int[] dp = new int[5];
dp[1] = -prices[0];
dp[3] = -prices[0];
for (int i = 1; i < prices.length; i++) {
dp[1] = Math.max(dp[1], -prices[i] + dp[0]);
dp[2] = Math.max(dp[2], prices[i] + dp[1]);
dp[3] = Math.max(dp[3], -prices[i] + dp[2]);
dp[4] = Math.max(dp[4], prices[i] + dp[3]);
}
return dp[4];
}
188. 买卖股票的最佳时机IV
(最多买卖k次)
动态规划
/**
* 思路: 动态规划
* 1. 确定dp数组以及下标含义
* dp[i][j] : 表示第i天状态为j时,所获得的最大利润
* j的范围是0~2*k, dp[i][0]表示不操作, dp[i][1]表示第一次买入, dp[i][2]表示第一次卖出, dp[i][3]表示第二次买入, dp[i][4]表示第二次卖出
* 所以很容易看出来, 奇数就是买入, 偶数就出卖出
* 2. 确定递推公式
* dp[i][1] = Math.max(dp[i-1][1], -prices[i] + dp[i-1][0]);
* dp[i][2] = Math.max(dp[i-1][2], prices[i] + dp[i-1][1]);
* dp[i][3] = Math.max(dp[i-1][3], -prices[i] + dp[i-1][2]);
* dp[i][4] = Math.max(dp[i-1][4], prices[i] + dp[i-1][3]);
* 3. 初始化
* dp[0][0] = 0
* dp[0][1] = -prices[0]
* 4. 遍历顺序
* 从前往后遍历
*
* 时间: O(n * k)
* 空间: O(n * k)
*/
public int maxProfit(int k, int[] prices) {
if(prices == null || prices.length == 0) {
return 0;
}
int[][] dp = new int[prices.length][2 * k + 1];
for (int j = 1; j < 2 * k + 1; j += 2) {
dp[0][j] = -prices[0];
}
for (int i = 1; i < prices.length; i++) {
for (int j = 1; j < 2 * k + 1; j += 2) {
// j为奇数时,表示买入
dp[i][j] = Math.max(dp[i-1][j], -prices[i] + dp[i-1][j-1]);
// j为偶数时,表示卖出
dp[i][j+1] = Math.max(dp[i-1][j+1], prices[i] + dp[i-1][j]);
}
}
return dp[prices.length-1][2*k];
}
动态规划优化
// 动态规划 + 空间优化到一维
// 时间: O(n * k) 空间: O(k)
public int maxProfit(int k, int[] prices) {
if(prices == null || prices.length == 0) {
return 0;
}
int[] dp = new int[2 * k + 1];
for (int j = 1; j < 2 * k + 1; j += 2) {
dp[j] = -prices[0];
}
for (int i = 1; i < prices.length; i++) {
for (int j = 1; j < 2 * k + 1; j += 2) {
// j为奇数时,表示买入
dp[j] = Math.max(dp[j], -prices[i] + dp[j-1]);
// j为偶数时,表示卖出
dp[j+1] = Math.max(dp[j+1], prices[i] + dp[j]);
}
}
return dp[2*k];
}
309. 最佳买卖股票时机含冷冻期
(买卖多次,卖出有一天冷冻期)
动态规划
/**
* 思路: 动态规划
* 1. 确定dp数组以及下标含义: dp[i][j]表示第i天状态为j时,所获得的最大利润值
* j的范围是0~3
* dp[i][0]:状态一 表示买入股票状态(今天买入, 之前买入了股票然后没有操作保持到今天还是买入状态)
* 今天买入又分两种情况:
* - 前一天是冷冻期(状态四)
* - 前一天是保持卖出的状态(状态二)
* dp[i][1]:状态二 表示保持卖出股票状态(两天前就已卖出了股票, 度过了冷冻期, 一直没有操作保持到今天还是卖出状态)
* 分两种情况:
* - 前一天是卖出(状态二)
* - 前一天是冷冻期(状态四)
* dp[i][2]:状态三 表示今天是卖出股票状态(今天卖出)
* - 昨天一定是买入的状态(状态一)
* dp[i][3]:状态四 表示今天为冷冻期,冷冻期只有一天
* - 昨天一定是卖出的状态(状态三)
*
* 为什么要把卖出拆为两个状态(状态二和状态三), 而买入可以用一个状态(状态一)表示?
* 因为有个冷冻期状态表示卖出的情况, 而且为了满足状态一中的今天买入情况以及状态四中的情况, 所以要拆分出来卖出两个状态
*
* 2. 确定递推公式:
* dp[i][0] = Math.max(-prices[i] + Math.max(dp[i-1][3], dp[i-1][1]), dp[i-1][0])
* dp[i][1] = Math.max(dp[i-1][1], dp[i-1][3])
* dp[i][2] = prices[i] + dp[i-1][0]
* dp[i][3] = dp[i-1][2]
* 3. 初始化
* dp[i][0] = -prices[0]
* dp[i][1] = 0
* dp[i][2] = 0
* dp[i][3] = 0
* 4. 确定遍历顺序
* 从前往后遍历
*
* 5. 返回最大值时 需要返回状态二、状态三、状态四其中的最大值
* 时间: O(n)
* 空间: O(n)
*/
public int maxProfit(int[] prices) {
int[][] dp = new int[prices.length][4];
dp[0][0] = -prices[0];
for (int i = 1; i < prices.length; i++) {
dp[i][0] = Math.max(-prices[i] + Math.max(dp[i-1][3], dp[i-1][1]), dp[i-1][0]);
dp[i][1] = Math.max(dp[i-1][1], dp[i-1][3]);
dp[i][2] = prices[i] + dp[i-1][0菜鸟系列 Golang 实战 Leetcode —— 买卖股票的最佳时机系列(121. 买卖股票的最佳时机买卖股票的最佳时机 II