买卖股票
121. 买卖股票的最佳时机
题目说明
给定一个数组 prices
,它的第 i
个元素 prices[i]
表示一支给定股票第 i
天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0
。
实例1
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
实例2
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。
方法:一次遍历
按照题目的思路,我们只需要在历史的最低点购买股票,然后在最低点后面的最高点卖出,这样获得的利润是最高的。
我们可以用一个变量记录一个历史最低价格 minValue
,我们就可以假设自己的股票是在那天买的。那么我们在第 i
天卖出股票能得到的利润就是 prices[i] - minValue
。只要保证prices[i] - minValue
最大即可。
class Solution {
public int maxProfit(int[] prices) {
int ans = 0, minValue = prices[0];
for (int i = 1; i < prices.length; i++) {
if (prices[i] < minValue) {
minValue = prices[i];
} else {
ans = Math.max(ans, prices[i] - minValue);
}
}
return ans;
}
}
122. 买卖股票的最佳时机 II
题目描述
给定一个数组 prices
,其中 prices[i]
表示股票第 i
天的价格。
在每一天,你可能会决定购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以购买它,然后在 同一天 出售。 返回 你能获得的 最大 利润 。
实例1
输入: prices = [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
实例2
输入: prices = [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:
输入: prices = [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
方法一:动态规划
因为「不能同时参与多笔交易」,因此每天交易结束后只可能存在手里有一支股票或者没有股票的状态。
定义状态 $\textit{dp}[i][0]$表示第 $i$ 天交易完后手里没有股票的最大利润,$\textit{dp}[i][1]$表示第 $i$ 天交易完后手里持有一支股票的最大利润($i$ 从 $0$ 开始)。
考虑 $\textit{dp}[i][0]$的转移方程,如果这一天交易完后手里没有股票,那么可能的转移状态为前一天已经没有股票,即 $\textit{dp}[i-1][0]$,或者前一天结束的时候手里持有一支股票,即 $\textit{dp}[i-1][1]$,这时候我们要将其卖出,并获得 $\textit{prices}[i]$ 的收益。因此为了收益最大化,我们列出如下的转移方程: \(p[i][0]=max\{dp[i−1][0],dp[i−1][1]+prices[i]\}\) 再来考虑 $\textit{dp}[i][1]$,按照同样的方式考虑转移状态,那么可能的转移状态为前一天已经持有一支股票,即 $\textit{dp}[i-1][1]$,或者前一天结束时还没有股票,即 $\textit{dp}[i-1][0]$,这时候我们要将其买入,并减少 $\textit{prices}[i]$ 的收益。可以列出如下的转移方程:
\(dp[i][1]=max\{dp[i−1][1],dp[i−1][0]−prices[i]\}\) 对于初始状态,根据状态定义我们可以知道第 $0$ 天交易结束的时候 $\textit{dp}[0][0]=0$,$\textit{dp}[0][1]=-\textit{prices}[0]$。
class Solution {
public int maxProfit(int[] prices) {
int[][] dp = new int[prices.length][2];
dp[0][0] = 0;
dp[0][1] = -prices[0];
for (int i = 1; i < prices.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 dp[prices.length - 1][0];
}
}
优化:
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;
}
}
方法二:贪心
因为交易次数不受限,如果可以把所有的上坡全部收集到,一定是利益最大化的
class Solution {
public int maxProfit(int[] prices) {
int ans = 0;
for (int i = 0; i < prices.length - 1; i++) {
if (prices[i + 1] > prices[i]) {
ans += prices[i + 1] - prices[i];
}
}
return ans;
}
}
123. 买卖股票的最佳时机 III
题目描述
给定一个数组,它的第 i
个元素是一支给定的股票在第 i
天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
实例1
输入:prices = [3,3,5,0,0,3,1,4]
输出:6
解释:在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。
随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。
实例2
输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这个情况下, 没有交易完成, 所以最大利润为 0。
示例 4:
输入:prices = [1]
输出:0
方法:动态规划
由于我们最多可以完成两笔交易,因此在任意一天结束之后,我们会处于以下五个状态中的一种:
-
未进行过任何操作;
-
只进行过一次买操作;
-
进行了一次买操作和一次卖操作,即完成了一笔交易;
-
在完成了一笔交易的前提下,进行了第二次买操作;
-
完成了全部两笔交易。
由于第一个状态的利润显然为 $0$,因此我们可以不用将其记录。对于剩下的四个状态,我们分别将它们的最大利润记为 $\textit{buy}_1$,$sell 1$,$\textit{buy}_2$,$ \textit{sell}_2$ 。
对于 $\textit{buy}_1$而言,在第 $i$ 天我们可以不进行任何操作,保持不变,也可以在未进行任何操作的前提下以 $\textit{prices}[i]$ 的价格买入股票,那么 $\textit{buy}_1$ 的状态转移方程即为: \(buy _1 =max\{buy_1',−prices[i]\}\) 用 $\textit{buy}_1’$表示第$i−1$ 天的状态,以便于和第 $i$ 天的状态 $\textit{buy}_1$进行区分。
同理我们可以得到其余的状态转移方程 \(sell_1 = max\{ sell'_1,buy_1 + prices[i] \} \\ buy_2 = max\{ buy'_2, sell'_1 - prices[i] \} \\ sell_2 = max\{ sell'_2, buy'_2 + prices[i] \}\) 对于边界条件,我们考虑第 $i=0$ 天时的四个状态:$\textit{buy}_1$即为以 $\textit{prices}[0]$ 的价格买入股票,因此 $\textit{buy}_1=-\textit{prices}[0]$;$\textit{sell}_1$即为在同一天买入并且卖出,因此 $\textit{sell}_1=0$;$\textit{buy}_2$即为在同一天买入并且卖出后再以 $\textit{prices}[0]$ 的价格买入股票,因此 $\textit{buy}_2=-\textit{prices}[0]$;同理可得 $\textit{sell}_2=0$。我们将这四个状态作为边界条件,从 $i=1$ 开始进行动态规划,即可得到答案。
在动态规划结束后,由于我们可以进行不超过两笔交易,因此最终的答案在 $0$,$\textit{sell}_1$,$\textit{sell}_2$中,且为三者中的最大值。然而我们可以发现,由于在边界条件中 $\textit{sell}_1$和 $\textit{sell}_2$ 的值已经为 $0$,并且在状态转移的过程中我们维护的是最大值,因此 $\textit{sell}_1$ 和 $\textit{sell}_2$最终一定大于等于 $0$。同时,如果最优的情况对应的是恰好一笔交易,那么它也会因为我们在转移时允许在同一天买入并且卖出这一宽松的条件,从 $\textit{sell}_1$转移至 $\textit{sell}_2$,因此最终的答案即为 $\textit{sell}_2$ 。
class Solution {
public int maxProfit(int[] prices) {
int length = prices.length;
int buy1 = -prices[0], sell1 = 0;
int buy2 = -prices[0], sell2 = 0;
for (int i = 1; i < length; i++) {
buy1 = Math.max(buy1, -prices[i]);
sell1 = Math.max(sell1, buy1 + prices[i]);
buy2 = Math.max(buy2, sell1 - prices[i]);
sell2 = Math.max(sell2, buy2 + prices[i]);
}
return sell2;
}
}