- Published on
LeetCode-121.买卖股票的最佳时机
- Authors

- Name
- Piggy DP
- @xiaozhudxiaozhu
声明:该文通过我个人写Prompt喂给AI产出
问题描述回顾
给定一个数组 prices,其中 prices[i] 表示第 i 天的股票价格。我们需要选择某一天买入,并在未来的某一天卖出,计算能够获得的最大利润。如果不能获得利润(即价格一直下跌),则返回 0。
示例
- 示例 1:
- 输入:
prices = [7,1,5,3,6,4] - 输出:
5 - 解释:在第 2 天(价格 = 1)买入,在第 5 天(价格 = 6)卖出,利润 = 6 - 1 = 5。
- 输入:
- 示例 2:
- 输入:
prices = [7,6,4,3,1] - 输出:
0 - 解释:价格一直在下跌,没有交易机会,利润为 0。
- 输入:
核心思想
这个问题可以通过一次遍历数组来解决,核心思想是:
- 记录当前遇到的最小价格:在遍历过程中,我们始终记录到目前为止遇到的最小价格,这个价格是潜在的买入点。
- 计算当前价格与最小价格的差值:对于每一天的价格,我们计算如果在这一天卖出,能够获得的利润(即当前价格减去之前的最小价格)。
- 更新最大利润:在遍历过程中,我们不断更新能够获得的最大利润。
为什么这种方法有效?
- 我们需要找到
prices[j] - prices[i]的最大值,其中j > i。 - 如果我们固定
prices[j],那么为了使prices[j] - prices[i]最大,prices[i]应该是prices[0..j-1]中的最小值。 - 因此,在遍历数组时,对于每一天的价格
prices[j],我们只需要知道之前的最小价格,就可以计算当前的最大利润。 - 因为我们每天都计算当前的最大利润,并加以维护,所以最后获得maxProfit就是最大利润。
关键点
- 初始化:
minPrice初始化为第一天的价格或一个很大的值(如Integer.MAX_VALUE)。maxProfit初始化为 0。
- 遍历过程:
- 更新
minPrice:如果当前价格比minPrice小,则更新minPrice。 - 计算当前利润:
currentProfit = prices[i] - minPrice。 - 更新
maxProfit:如果currentProfit大于maxProfit,则更新maxProfit。
- 更新
- 结果:
- 遍历结束后,
maxProfit就是能够获得的最大利润。
- 遍历结束后,
Java 实现
class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length <= 1) {
return 0;
}
int maxProfit = 0;
int minPrice = Integer.MAX_VALUE;
for (int i = 0; i < prices.length; i++) {
minPrice = Math.min(minPrice, prices[i]);
maxProfit = Math.max(maxProfit, prices[i] - minPrice);
}
return maxProfit;
}
}
代码解释
- 初始化:
minPrice初始化为Integer.MAX_VALUE,这样任何价格都会比它小,从而在第一次比较时被更新。maxProfit初始化为 0,表示初始时没有利润。
- 遍历数组:
- 对于每一天的价格
price:- 如果
price比minPrice小,说明找到了更低的买入点,更新minPrice。 - 计算
price - minPrice(即当前利润),如果比maxProfit大,则更新maxProfit。
- 如果
- 对于每一天的价格
- 返回结果:
- 遍历结束后,
maxProfit就是能够获得的最大利润。
- 遍历结束后,
复杂度分析
- 时间复杂度:O(n),因为我们只遍历数组一次。
- 空间复杂度:O(1),只使用了常数空间。
示例演示
以 prices = [7,1,5,3,6,4] 为例:
- 初始:
minPrice = MAX_VALUE,maxProfit = 0 - i=0: price=7
minPrice = 7,maxProfit = 0
- i=1: price=1
minPrice = 1,maxProfit = 0
- i=2: price=5
5 - 1 = 4 > 0=>maxProfit = 4
- i=3: price=3
3 - 1 = 2 < 4=>maxProfit不变
- i=4: price=6
6 - 1 = 5 > 4=>maxProfit = 5
- i=5: price=4
4 - 1 = 3 < 5=>maxProfit不变
- 最终结果:
5
边界情况
- 空数组或单元素数组:
- 直接返回 0,因为没有交易机会。
- 价格一直下跌:
maxProfit始终为 0。
- 价格一直上涨:
minPrice始终是第一个价格,maxProfit是最后一个价格减去第一个价格。
总结
这个问题通过一次遍历和贪心的思想(始终记录当前最小价格和最大利润)高效地解决了问题。关键在于理解如何通过维护最小价格来简化计算最大利润的过程。
动态规划解法思路
虽然这道题可以用贪心算法高效解决,但也可以用动态规划(DP)的思想来解决。动态规划的思路是定义状态并找到状态转移方程。
状态定义
我们可以定义以下状态:
dp[i][0]:表示第i天结束时不持有股票时的最大利润。dp[i][1]:表示第i天结束时持有股票时的最大利润。
初始状态
dp[0][0] = 0:第 0 天不持有股票,利润为 0。dp[0][1] = -prices[0]:第 0 天持有股票,说明在第 0 天买入,利润为-prices[0](因为花钱买入,所以是负的)。
状态转移方程
dp[i][0](第i天不持有股票):- 可能是前一天就不持有股票:
dp[i-1][0]。 - 也可能是前一天持有股票,今天卖出:
dp[i-1][1] + prices[i]。 - 取两者的最大值:
dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])。
- 可能是前一天就不持有股票:
dp[i][1](第i天持有股票):- 可能是前一天就持有股票:
dp[i-1][1]。 - 也可能是今天才买入股票(因为只能交易一次,所以之前不能有交易):
-prices[i]。 - 取两者的最大值:
dp[i][1] = max(dp[i-1][1], -prices[i])。
- 可能是前一天就持有股票:
最终结果
最后一天不持有股票时的利润就是最大利润,即 dp[n-1][0](因为卖出股票后才能获得利润)。
Java 实现
public class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0) {
return 0;
}
int n = prices.length;
int[][] dp = new int[n][2];
// 初始状态
dp[0][0] = 0;
dp[0][1] = -prices[0];
for (int i = 1; i < n; i++) {
// 第 i 天不持有股票:前一天就不持有 或 前一天持有今天卖出
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i]);
// 第 i 天持有股票:前一天就持有 或 今天买入(因为只能交易一次,所以之前不能有交易)
dp[i][1] = Math.max(dp[i-1][1], -prices[i]);
}
return dp[n-1][0]; // 最后一天不持有股票时的利润
}
}
空间优化
由于 dp[i] 只依赖于 dp[i-1],我们可以用两个变量代替二维数组,优化空间复杂度到 O(1):
public class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length == 0) {
return 0;
}
int dp0 = 0; // 不持有股票时的利润
int dp1 = -prices[0]; // 持有股票时的利润
for (int i = 1; i < prices.length; i++) {
dp0 = Math.max(dp0, dp1 + prices[i]);
dp1 = Math.max(dp1, -prices[i]);
}
return dp0;
}
}
动态规划与贪心的对比
- 贪心算法:
- 时间复杂度:O(n),空间复杂度:O(1)。
- 核心思想:遍历时记录最小价格,并计算当前价格与最小价格的差值。
- 动态规划:
- 时间复杂度:O(n),空间复杂度:O(1)(优化后)。
- 核心思想:定义状态并推导状态转移方程,更通用(适用于更复杂的股票买卖问题)。
为什么动态规划也能解决这个问题?
虽然贪心算法更高效,但动态规划的思路更通用,可以扩展到更复杂的股票买卖问题(如多次交易、含手续费等)。本题的动态规划解法可以看作是贪心算法的另一种形式,因为 dp0 和 dp1 的更新逻辑与贪心算法中的 minPrice 和 maxProfit 类似。
示例演示
以 prices = [7,1,5,3,6,4] 为例:
- 初始:
dp0 = 0,dp1 = -7
- i=1: price=1
dp0 = max(0, -7 + 1) = max(0, -6) = 0dp1 = max(-7, -1) = -1
- i=2: price=5
dp0 = max(0, -1 + 5) = max(0, 4) = 4dp1 = max(-1, -5) = -1
- i=3: price=3
dp0 = max(4, -1 + 3) = max(4, 2) = 4dp1 = max(-1, -3) = -1
- i=4: price=6
dp0 = max(4, -1 + 6) = max(4, 5) = 5dp1 = max(-1, -6) = -1
- i=5: price=4
dp0 = max(5, -1 + 4) = max(5, 3) = 5dp1 = max(-1, -4) = -1
- 最终结果:
dp0 = 5
总结
- 贪心算法:简单高效,适用于本题。
- 动态规划:通用性强,适用于更复杂的股票买卖问题。
- 两种方法的时间复杂度都是 O(n),空间复杂度都是 O(1)(优化后)。