买卖股票的最佳时机(Best Time to Buy and Sell Stock)
1. 题目描述
题目名称:买卖股票的最佳时机
题目描述:给定一个数组 prices,其中 prices[i]表示股票第 i天的价格。你只能选择某一天买入并在未来的某一天卖出。设计一个算法来计算你所能获取的最大利润。如果你不能获取任何利润,返回 0。
输入:prices = [7,1,5,3,6,4]
输出:5
解释:在第 2 天买入(价格 = 1),在第 5 天卖出(价格 = 6),最大利润 = 6-1 = 5。注意不能在第 2 天买入,第 1 天卖出,因为卖出必须在买入之后。
约束条件:
1 <= prices.length <= 10^50 <= prices[i] <= 10^4
示例:
输入: [7,1,5,3,6,4] → 输出: 5
输入: [7,6,4,3,1] → 输出: 0 (无法盈利)
2. 解题思路分析
方法一:暴力解法
思路:对于每一天作为买入日,遍历其后的所有天作为卖出日,计算所有可能的利润,取最大值。
时间复杂度:O(n²)
空间复杂度:O(1)
#include<iostream>
#include<vector>
using namespace std;
int main(){
vector<int> prices = {7,1,5,3,6,4};
int max = 0;
//暴力解
for(int i = 0; i < prices.size(); i++){
for(int j = i+1; j < prices.size(); j++){
if(prices[j] > prices[i]){
if(max < prices[j] - prices[i]){
max = prices[j] - prices[i];
}
}
}
}
cout << max << endl;
//时间复杂度O(n^2)
}
方法二:双指针思路
优化点:使用双指针模拟买入卖出过程,左指针记录历史最低价,右指针遍历寻找最大差价
核心思想:维护一个买入指针指向历史最低价,遍历时如果找到更低价格则更新买入点,否则计算当前利润
时间复杂度:O(n)
空间复杂度:O(1)
#include<iostream>
#include<vector>
#include<algorithm> // 需要包含algorithm头文件使用max函数
using namespace std;
int main(){
vector<int> prices = {7,1,5,3,6,4};
if (prices.size() < 2) {
cout << 0 << endl;
return 0;
}
int maxProfit = 0;
int minBuy = 0; // 买入指针(指向历史最低价的索引)
for(int i = 1; i < prices.size(); i++){ // i从1开始遍历
if (prices[i] > prices[minBuy]) {
// 当前价格高于买入价,计算利润
maxProfit = max(maxProfit, prices[i] - prices[minBuy]);
} else {
// 找到更低的买入点,更新minBuy
minBuy = i;
}
}
cout << maxProfit << endl;
return 0;
}
方法三:一次遍历优化(贪心算法)
优化点:只需要遍历一次,维护两个变量:
minPrice:记录历史最低价格(作为买入候选)maxProfit:记录当前最大利润
核心思想:对于每一天,如果价格低于历史最低价,更新买入点;否则计算当前卖出利润并更新最大利润。
时间复杂度:O(n)
空间复杂度:O(1)
#include<iostream>
#include<vector>
#include<algorithm> // 需要包含algorithm头文件使用min/max函数
using namespace std;
int main(){
vector<int> prices = {7,1,5,3,6,4};
int minPrice = prices[0]; // 记录买入最小价格
int maxProfit = 0; // 记录最大利润
for(int i = 1; i < prices.size(); i++){ // 遍历价格数组
minPrice = min(minPrice, prices[i]); // 更新买入最小价格
maxProfit = max(maxProfit, prices[i] - minPrice); // 更新最大利润
}
cout << maxProfit << endl;
// 贪心算法
// 时间复杂度:O(n)
return 0;
}
3. 核心思想与关键问题解答
Q1: 为什么选择贪心算法而不是动态规划?
A: 本题只需要一次交易,且状态转移简单:每天只需要记录历史最低价和当前最大利润。贪心算法更直观高效,而动态规划会引入不必要的状态复杂度。
Q2: 为什么用一次遍历就能解决问题?
A: 因为利润计算只依赖于历史最低价和当前价格。我们不需要知道具体哪一天买入卖出,只需要保证买入在卖出之前,且买入价是历史最低。
Q3: 该算法的适用场景是什么?
A: 适用于单次交易的股票买卖问题。对于多次交易、含冷冻期、含手续费等变体,需要更复杂的动态规划方法。
Q4: 如何扩展到同类问题?
A: 对于"买卖股票的最佳时机 II"(允许多次交易),可以使用贪心累加所有上涨区间;对于"含手续费"或"冷冻期"问题,需要使用状态机DP。
4. 算法流程详解
以输入 prices = [7,1,5,3,6,4]为例:
| 步骤 | 当前索引 | 当前价格 | 历史最低价(minPrice) | 当前利润计算 | 最大利润(maxProfit) | 说明 |
|---|---|---|---|---|---|---|
| 初始化 | - | - | 7 | - | 0 | 初始化minPrice为prices[0] |
| 1 | 1 | 1 | 1 | 1-7=-6 | 0 | 价格1<7,更新minPrice |
| 2 | 2 | 5 | 1 | 5-1=4 | 4 | 计算利润,更新maxProfit |
| 3 | 3 | 3 | 1 | 3-1=2 | 4 | 利润较小,不更新 |
| 4 | 4 | 6 | 1 | 6-1=5 | 5 | 计算利润,更新maxProfit |
| 5 | 5 | 4 | 1 | 4-1=3 | 5 | 利润较小,不更新 |
最终输出:5
5. 总结 / 适用场景扩展
核心技巧:
- 使用贪心思想,维护历史最低价和最大利润
- 时间复杂度O(n),空间复杂度O(1),非常高效
同类题型扩展:
- 买卖股票的最佳时机 II(允许多次交易):累加所有上涨区间
- 买卖股票的最佳时机 III(最多两次交易):需要二维DP
- 买卖股票的最佳时机 IV(最多k次交易):通用DP解法
- 含冷冻期/手续费:状态机动态规划
6. 边界情况 / 测试用例 / 变体思考
边界测试用例:
- 空数组:
[]→ 返回0 - 单元素:
[5]→ 返回0 - 价格一直下跌:
[7,6,4,3,1]→ 返回0 - 价格全相同:
[3,3,3,3]→ 返回0 - 只有两天且盈利:
[1,2]→ 返回1