给定一个数组,它的第 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。
题解 1 (超内存限制)
//官方提示的是用动态规划,最开始想得到是dp[i][j] 表示我们从i到j的最大利润,最够超出内存限制
public int maxProfit(int[] prices) {
//dp表示i,j这段时间的最大利润
int max = 0;
if (prices.length == 0) return max;
int[][] dp = new int[prices.length][prices.length];
for (int i = 0; i < prices.length - 1; i++) {
dp[i][i] = 0;
dp[i][i + 1] = prices[i + 1] - prices[i];
max = dp[i][i + 1] > max ? dp[i][i + 1] : max;
}
dp[prices.length - 1][prices.length - 1] = 0;
for (int len = 2; len <= prices.length; len++) {
for (int start = 0; start < prices.length; start++) {
if (start + len >= prices.length) {
break;
}
int end = start + len;
if ((prices[end] - prices[start]) > dp[start + 1][end - 1]) {
dp[start][end] = (prices[end] - prices[start]);
} else {
dp[start][end] = dp[start + 1][end - 1];
}
max = dp[start][end] > max ? dp[start][end] : max;
}
}
return (int) max;
}
题解2
其实这道题我们可以使用一维数组的动态规划实现,因为每两个相邻的数组存在差值,我们可以构建一个diff数组,然后求这些数组最大连续子序列和,即是我们的答案
//dp[i]表示以i结尾最大子序列和
//动态转移方程 dp[i] = dp[i - 1] + diff[i] > diff[i] ? dp[i - 1] + diff[i] : diff[i];
public int maxProfit(int[] prices) {
if (prices.length == 0||prices.length==1)
return 0;
int[] diff = new int[prices.length - 1];
for (int i = 0; i < prices.length - 1; i++) {
diff[i] = prices[i + 1] - prices[i];
}
int[] dp = new int[diff.length];
dp[0] = diff[0];
int max=dp[0]>0?dp[0]:0;
for (int i = 1; i < diff.length; i++) {
dp[i] = dp[i - 1] + diff[i] > diff[i] ? dp[i - 1] + diff[i] : diff[i];
max = max > dp[i] ? max : dp[i];
}
return max;
}
题解3(在Leetcode官网上找到的归纳,状态机做法)
作者:labuladong
链接:https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/solution/yi-ge-fang-fa-tuan-mie-6-dao-gu-piao-wen-ti-by-l-3/
来源:力扣(LeetCode)
其实我们可以把我们的买卖股票状态进行抽象
/*
dp[i][k][flag]
i表示第i天
k表示我们可以进行交易的次数
flag 表示我们的交易行为,1标售我们手里有股票,0表示我们手里没有股票
用上述三维数组可以表示所有的状态
确定状态转移方程
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
max(选择 rest ,选择 sell)
解释:今天我没有持有股票,有两种可能:
要么是我昨天就没有持有,然后今天选择 rest,所以我今天还是没有持有;
要么是我昨天持有股票,但是今天我 sell 了,所以我今天没有持有股票了。
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
max(选择 rest ,选择 buy)
解释:今天我持有着股票,有两种可能:
要么我昨天就持有着股票,然后今天选择 rest,所以我今天还持有着股票;
要么我昨天本没有持有,但今天我选择 buy,所以今天我就持有股票了。
*/
当k=1(只允许交易一次)
/*
边界条件
dp[-1][k][0] = 0
解释:因为 i 是从 0 开始的,所以 i = -1 意味着还没有开始,这时候的利润当然是 0 。
dp[-1][k][1] = -infinity
解释:还没开始的时候,是不可能持有股票的,用负无穷表示这种不可能。
dp[i][0][0] = 0
解释:因为 k 是从 1 开始的,所以 k = 0 意味着根本不允许交易,这时候利润当然是 0 。
dp[i][0][1] = -infinity
解释:不允许交易的情况下,是不可能持有股票的,用负无穷表示这种不可能。
dp[i][1][0] = max(dp[i-1][1][0], dp[i-1][1][1] + prices[i])
dp[i][1][1] = max(dp[i-1][1][1], dp[i-1][0][0] - prices[i])
= max(dp[i-1][1][1], -prices[i])
解释:k = 0 的 base case,所以 dp[i-1][0][0] = 0。
现在发现 k 都是 1,不会改变,即 k 对状态转移已经没有影响了。
可以进行进一步化简去掉所有 k:
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
dp[i][1] = max(dp[i-1][1], -prices[i])
*/
base case:
dp[-1][k][0] = dp[i][0][0] = 0
dp[-1][k][1] = dp[i][0][1] = -infinity
状态转移方程:
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
public int maxProfit(int[] prices) {
if(prices.length==0) return 0;
int[][] dp = new int[prices.length][2];
for (int i = 0; i < prices.length; i++) {
if (i == 0) {
//没买了股票
dp[i][0] = 0;
//买了股票
dp[i][1] = -prices[i];
continue;
}
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[prices.length - 1][0];
}
当k=n(可以一直交易)
/*
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
= max(dp[i-1][k][1], dp[i-1][k][0] - prices[i])
注意当k=n无穷大时,k等价于k-1都表示可以一直交易
!!!!我们的买入卖出记为一次交易,因此只买入算一次交易,卖出不在计算,因此买的时候可交易次数减一
卖的时候,交易次数不变
*/
public int maxProfit(int[] prices) {
if (prices.length == 0) return 0;
int[][] dp = new int[prices.length][2];
for (int i = 0; i < prices.length; i++) {
if (i == 0) {
dp[i][0] = 0;
dp[i][1] = -prices[i];
continue;
}
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[prices.length - 1][0];
}
//代码是可以简化的,这里为了比较清除的理解,所以这里没有简化的,有兴趣可以看看原作者的分析
当k=n(冷冻期,卖出后,必须至少等一天才能购买)
/*
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
= max(dp[i-1][k][1], dp[i-2][k][0] - prices[i])
注意当k=n无穷大时,k等价于k-1都表示可以一直交易
!!!由于我们卖出后,不能连续再次购买,必须至少再等一次,才能进行购买 i-2
转移函数基本一致,由于存在i-2我们需要对这部分初始值进行处理
*/
public int maxProfit(int[] prices) {
if (prices.length == 0) return 0;
int[][] dp = new int[prices.length][2];
for (int i = 0; i < prices.length; i++) {
if (i == 0) {
dp[i][0] = 0;
dp[i][1] = -prices[i];
continue;
}
if (i == 1) {
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);//原始不变
dp[i][1] = Math.max(dp[i - 1][1]);//前一天就有了,第二种情况不存在,
continue;
}
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 - 2][0] - prices[i]);//不动、买进
}
return dp[prices.length - 1][0];
}
k=2只允许交易两次
public int maxProfit(int[] prices) {
if (prices.length == 0) return 0;
int k = 2;//交易次数
int[][][] dp = new int[prices.length][k + 1][2];
for (int i = 0; i < prices.length; i++) {
if (i == 0) {
//basecase
dp[i][2][0] = 0;//不可能
dp[i][2][1] = -prices[i];//不可能
dp[i][1][0] = 0;//不可能
dp[i][1][1] = -prices[i];//买入
continue;
}
dp[i][2][0] = Math.max(dp[i - 1][2][0], dp[i - 1][2][1] + prices[i]);//卖
dp[i][2][1] = Math.max(dp[i - 1][2][1], dp[i - 1][1][0] - prices[i]);//买
dp[i][1][0] = Math.max(dp[i - 1][1][0], dp[i - 1][1][1] + prices[i]);//卖
dp[i][1][1] = Math.max(dp[i - 1][1][1], 0 - prices[i]);//买
}
return dp[prices.length - 1][k][0];
}
k=任意值
public int maxProfit(int k, int[] prices) {
if (k == 0 || prices.length == 0) return 0;
if (k > prices.length / 2) {
//k为无穷的情况,与之前的代码重复
int[][] dp = new int[prices.length][2];
for (int i = 0; i < prices.length; i++) {
if (i == 0) {
dp[i][0] = 0;
dp[i][1] = -prices[i];
continue;
}
dp[i][1] = Math.max(dp[i - 1][1], dp[i-1][0] - prices[i]);//买入
dp[i][0] = Math.max(dp[i - 1][0], dp[i-1][1] + prices[i]);//卖出
}
return dp[prices.length - 1][0];
}
int[][][] dp = new int[prices.length][k + 1][2];
for (int i = 0; i < prices.length; i++) {
for (int j = 1; j <= k; j++) {
if (i == 0) {
dp[i][j][1] = -prices[i];
dp[i][j][0] = 0;
continue;
}
dp[i][j][1] = Math.max(dp[i - 1][j][1], dp[i - 1][j - 1][0] - prices[i]);
dp[i][j][0] = Math.max(dp[i - 1][j][0], dp[i - 1][j][1] + prices[i]);
}
}
return dp[prices.length - 1][k][0];
}