121. 买卖股票的最佳时机——只允许交易一次 动态规划 || 一次遍历
给定一个数组 prices
,它的第 i
个元素 prices[i]
表示一支给定股票第 i
天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0
。
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
第二种动态规划
public class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
// 特殊判断
if (len < 2) {
return 0;
}
int[][] dp = new int[len][2];
// dp[i][0] 下标为 i 这天结束的时候,不持股,手上拥有的现金数
// dp[i][1] 下标为 i 这天结束的时候,持股,手上拥有的现金数
// 初始化:不持股显然为 0,持股就需要减去第 1 天(下标为 0)的股价
dp[0][0] = 0;
dp[0][1] = -prices[0];
// 从第 2 天开始遍历
for (int i = 1; i < len; 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], -prices[i]);
}
return dp[len - 1][0];
}
}
122. 买卖股票的最佳时机 II —— 允许交易无数次,贪心||动态规划
给定一个数组 prices
,其中 prices[i]
是一支给定股票第 i
天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)
输入: prices = [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
贪心算法:
时间复杂度 O(N) : 只需遍历一次price;
空间复杂度 O(1): 变量使用常数额外空间。
class Solution {
public int maxProfit(int[] prices) {
int profit = 0;
for (int i = 1; i < prices.length; i++) {
int tmp = prices[i] - prices[i - 1];
if (tmp > 0) profit += tmp;
}
return profit;
}
}
动态规划:
dp[i][j] 表示到下标为 i 的这一天,持股状态为 j 时,我们手上拥有的最大现金数。
确定初始值:起始如果什么都不做,dp[0][0] = 0;如果持有股票,当前拥有的现金数是当天股价的相反数,即 dp[0][1] = -prices[i];
时间复杂度:O(N),这里 NN 表示股价数组的长度;
空间复杂度:O(N),虽然是二维数组,但是第二维是常数,与问题规模无关。
public class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
if (len < 2) {
return 0;
}
// 0:持有现金
// 1:持有股票
// 状态转移:0 → 1 → 0 → 1 → 0 → 1 → 0
int[][] dp = new int[len][2];
dp[0][0] = 0;
dp[0][1] = -prices[0];
for (int i = 1; i < len; 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 dp[len - 1][0];
}
}
123. 买卖股票的最佳时机 III —— 最多买卖2次 动态规划 || 头尾双指针
给定一个数组,它的第i
个元素是一支给定的股票在第 i
天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 **两笔 **交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
输入:prices = [3,3,5,0,0,3,1,4]
输出:6
解释:在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。
随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。
第一种:动态规划
一天结束时,可能有持股、可能未持股、可能卖出过1次、可能卖出过2次、也可能未卖出过
所以定义状态转移数组dp[天数][当前是否持股][卖出的次数]
具体一天结束时的6种状态:
- 未持股,未卖出过股票:说明从未进行过买卖,利润为0
dp[i][0][0]=0 - 未持股,卖出过1次股票:可能是今天卖出,也可能是之前卖的(昨天也未持股且卖出过)
dp[i][0][1]=max(dp[i-1][1][0]+prices[i],dp[i-1][0][1]) - 未持股,卖出过2次股票:可能是今天卖出,也可能是之前卖的(昨天也未持股且卖出过)
dp[i][0][2]=max(dp[i-1][1][1]+prices[i],dp[i-1][0][2]) - 持股,未卖出过股票:可能是今天买的,也可能是之前买的(昨天也持股)
dp[i][1][0]=max(dp[i-1][0][0]-prices[i],dp[i-1][1][0]) - 持股,卖出过1次股票:可能是今天买的,也可能是之前买的(昨天也持股)
dp[i][1][1]=max(dp[i-1][0][1]-prices[i],dp[i-1][1][1]) - 持股,卖出过2次股票:最多交易2次,这种情况不存在
dp[i][1][2]=float('-inf')
class Solution:
def maxProfit(self, prices):
if prices==[]:
return 0
length=len(prices)
#结束时的最高利润=[天数][是否持有股票][卖出次数]
dp=[ [[0,0,0],[0,0,0] ] for i in range(0,length) ]
#第一天休息
dp[0][0][0]=0
#第一天买入
dp[0][1][0]=-prices[0]
# 第一天不可能已经有卖出
dp[0][0][1] = float('-inf')
dp[0][0][2] = float('-inf')
#第一天不可能已经卖出
dp[0][1][1]=float('-inf')
dp[0][1][2]=float('-inf')
for i in range(1,length):
#未持股,未卖出过,说明从未进行过买卖
dp[i][0][0]=0
#未持股,卖出过1次,可能是今天卖的,可能是之前卖的
dp[i][0][1]=max(dp[i-1][1][0]+prices[i],dp[i-1][0][1])
#未持股,卖出过2次,可能是今天卖的,可能是之前卖的
dp[i][0][2]=max(dp[i-1][1][1]+prices[i],dp[i-1][0][2])
#持股,未卖出过,可能是今天买的,可能是之前买的
dp[i][1][0]=max(dp[i-1][0][0]-prices[i],dp[i-1][1][0])
#持股,卖出过1次,可能是今天买的,可能是之前买的
dp[i][1][1]=max(dp[i-1][0][1]-prices[i],dp[i-1][1][1])
#持股,卖出过2次,不可能
dp[i][1][2]=float('-inf')
return max(dp[length-1][0][1],dp[length-1][0][2],0)
第二种:头尾双指针
以i为分界线,[0,i]最大 + [i+1,len-1]最大。
从左向右遍历很简单的能求出[0,i]最大,从左向右遍历过程中记录最小值,当前值与最小值求差,这个差的最大值就是[0,i]最大
类似的能求出[i+1,len-1]最大,倒着做。为了便于查找,用数组maxs[]记录每个位置右侧差的最大值。
实际编码中我选择先做[i+1,len-1]最大。在做[0,i]最大过程中,结合maxs[]求出答案。
class Solution {
public int maxProfit(int[] prices) {
int len = prices.length;
int max = 0;
int[] maxs = new int[len + 1];
for (int i = len - 1; i >= 0; i--) {
int price = prices[i];
max = Math.max(max, price);// [i,len-1]位置右侧的最大值
maxs[i] = Math.max(maxs[i + 1], max - price);// [i,len-1]最大差值
}
int min = Integer.MAX_VALUE;
max = 0;
for (int i = 0; i < len; i++) {
int price = prices[i];
min = Math.min(min, price);// [0,i]最小值
max = Math.max(max, price - min + maxs[i + 1]);// [0,i]最大值 + [i+1,len-1]最大值
}
return max;
}
}
class Solution {
public int maxProfit(int k, int[] prices) {
if (prices.length == 0) {
return 0;
}
int n = prices.length;
k = Math.min(k, n / 2);
int[][] buy = new int[n][k + 1];
int[][] sell = new int[n][k + 1];
buy[0][0] = -prices[0];
sell[0][0] = 0;
for (int i = 1; i <= k; ++i) {
buy[0][i] = sell[0][i] = Integer.MIN_VALUE / 2;
}
for (int i = 1; i < n; ++i) {
buy[i][0] = Math.max(buy[i - 1][0], sell[i - 1][0] - prices[i]);
for (int j = 1; j <= k; ++j) {
buy[i][j] = Math.max(buy[i - 1][j], sell[i - 1][j] - prices[i]);
sell[i][j] = Math.max(sell[i - 1][j], buy[i - 1][j - 1] + prices[i]);
}
}
return Arrays.stream(sell[n - 1]).max().getAsInt();
}
}