121.买卖股票的最佳时机
应当这样理解本题:
- 只要某天的股票价格高于买入的价格,那么就可以考虑卖出股票,如果以后有更好的方案则改变方案;
- 假如我打算在某天卖出股票,那么一定当股票是在其价格最低点买入的时候,利润才是最大的;
- 如果在今天之前的价格都高于当前价格,则考虑今天才买入,如果未来卖出,一定比之前任意一天买入的利润要高。
综上,任意一天需要考虑的应当是两个值:之前的最低价格和当前价格,当当前价格高于最低价格时考虑卖出,否则更新最低价格。
第一天的时候只能买入,不可卖出。
class Solution {
public int maxProfit(int[] prices) {
int profit = 0; // 最大利润
int minValue = prices[0];
for (int i : prices) {
if (i <= minValue) { //第一天的价格等于最低价,修改最低价格,所以下标可以从0开始
minValue = i;
} else {
profit = Math.max(profit, i - minValue);
}
}
return profit;
}
}
122. 买卖股票的最佳时机Ⅱ
714. 买卖股票的最佳时机含手续费
这两个题完全可以合并在一起,因为思路是一样的,122
题只是714
题中手续费为0
的特殊情况。
1.贪心算法
此处贪心算法的本质是允许后悔,即只要当前的价格可以产生利润,那就卖出,如果之后发现有更好的利润的时候就改变卖出的时机,换取更多的利润。重点在于,由于每次卖出之后允许后悔,即相当于每次卖出之后可以立即以当天的股票价格再买入股票(无需手续费),如果某天的股价加上手续费还低于手中股票的购入的费用,则可以重新购入股票,注意当天的股价应当包括手续费;如果某天的股价(包括手续费)等于买入价格,则不作处理,因为可能意味着多一次的手续费。
对于“由于每次卖出之后允许后悔,即相当于每次卖出之后可以立即以当天的股票价格再买入股票(无需手续费)” ,可以这样理解 ,假如手续费是fee,买入时股价为a,则买入价格是a+fee,假设未来股价b>a+fee,c>b,则显然a+fee的价格买入,c的价格时卖出利润最多,观察如下表达式:
c - (a+fee) = (c - b) + (b - (a+fee))
等式右边第一项即表示用b的价格(不含手续费)再次买入的收益。
//122. 不含手续费
class Solution {
public int maxProfit(int[] prices) {
int ans = 0;
int buy = prices[0]; // 假设第一天买入
for (int i = 1; i < prices.length; i++) {
if (prices[i] < buy) { // 当前的价格更低,如果换成今天买入,未来的收益更高(后悔,重新买入)
buy = prices[i];
} else if (prices[i] > buy) { // 当前的价格卖出有利润,立刻卖出,未来可以后悔
ans += prices[i] - buy;
buy = prices[i];
}
}
return ans;
}
}
// 上述程序可以简化
class Solution {
public int maxProfit(int[] prices) {
int ans = 0;
for (int i = 1; i < prices.length; i++) {
ans += Math.max(0, prices[i] - prices[i - 1]);
}
return ans;
}
}
//714. 考虑手续费
class Solution {
public int maxProfit(int[] prices, int fee) {
int ans = 0;
int buy = prices[0] + fee; // 假设第一天买入
for (int i = 1; i < prices.length; i++) {
if (prices[i] + fee < buy) { // 当前的价格更低,如果换成今天买入,未来的收益更高(后悔,重新买入)
buy = prices[i];
} else if (prices[i] > buy) { // 当前的价格卖出有利润,立刻卖出,未来可以后悔
ans += prices[i] - buy;
buy = prices[i];
}
}
return ans;
}
}
2. 动态规划
也可以使用动态规划的方法求解,即一共做n次决策,每次决策是买入还是卖出或者是等待(不买入或不卖出),每次决策的结果对应一种状态。每种状态分别包含手中有股票和没有股票时的最大利润。初始状态,只能考虑买入股票(手中有股票)或者不买入(手中没有股票),对应的利润值分别是-price[0]
和0
;以后的每一天状态如下 :
- 手中有股票 :前一天手中没有股票并在今天买入(如果有手续费则支付手续费);前一天手中有股票并且今天没有卖出。
- 手中没有股票:前一天手中有股票并在今天卖出;前一天手中没有股票并且今天没有买入。
如果用一个n*2
的数组dp
表示状态表,dp[i][0]
表示手中没有股票时的利润,dp[i][1]
表示手中有股票时的利润,则状态转移过程如下 :
dp[i][0]=max\{dp[i−1][0],dp[i−1][1]+prices[i]−fee \}
dp[i][1]=max\{dp[i−1][1],dp[i−1][0]−prices[i] \}
当然也可以在股票买入的时候支付手续费,对应初始状态和状态转移方程如下:
dp[0][0] = 0;
dp[0][1] = -prices[0] - fee;
dp[i][0]=max\{dp[i−1][0],dp[i−1][1]+prices[i] \}
dp[i][1]=max\{dp[i−1][1],dp[i−1][0]−prices[i]−fee \}
为了节省空间,考虑到每天的状态只与前一天的状态有关,所以只动态地维护前一天的状态即可。
// 未考虑手续费的程序
class Solution {
public int maxProfit(int[] prices) {
int n = prices.length;
int dp0 = 0, dp1 = -prices[0];
for (int i = 1; i < n; ++i) {
int newDp0 = Math.max(dp0, dp1 + prices[i]);
int newDp1 = Math.max(dp1, dp0 - prices[i]);
dp0 = newDp0;
dp1 = newDp1;
}
return dp0;
}
}
309. 最佳买股票时机含冷冻期
这里依然采用动态规划的方法求解,与前一题不同的是,第i
的利润情况分为三种状态:
dp[i][0]: 手上持有股票的最大利润;
- 手中的股票是前一天剩下的且今天没有买入;
- 手中的股票是今天买入的,此时的前提是昨天没有卖出(不可从dp[i-1][1]转来);
dp[i][1]: 手上不持有股票,今天卖出了股票(i+1天是冷冻期)的最大利润; - 需要前一天手中有股票,即从dp[i-1][0]状态转来
dp[i][2]: 手上不持有股票,之前某天卖出股票(i+1天非冷冻期)的最大利润; - 前一天手中没有股票且今天也没有买入。
状态转移方程如下:
f[i][0]=max(f[i−1][0],f[i−1][2]−prices[i])
f[i][1]=f[i−1][0]+prices[i]
f[i][2]=max(f[i−1][1],f[i−1][2])
class Solution {
public int maxProfit(int[] prices) {
if (prices.length == 0) {
return 0;
}
int n = prices.length;
int[][] dp = new int[n][3];
f[0][0] = -prices[0];
for (int i = 1; i < n; ++i) {
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][2] - prices[i]);
dp[i][1] = dp[i - 1][0] + prices[i];
dp[i][2] = Math.max(dp[i - 1][1], dp[i - 1][2]);
}
return Math.max(dp[n - 1][1], dp[n - 1][2]);
}
}