力扣HOT100刷题记录-贪心-题解-121.买卖股票的最佳时机

9 阅读5分钟

买卖股票的最佳时机(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^5
  • 0 <= 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]
11111-7=-60价格1<7,更新minPrice
22515-1=44计算利润,更新maxProfit
33313-1=24利润较小,不更新
44616-1=55计算利润,更新maxProfit
55414-1=35利润较小,不更新

最终输出:5

5. 总结 / 适用场景扩展

核心技巧

  • 使用贪心思想,维护历史最低价和最大利润
  • 时间复杂度O(n),空间复杂度O(1),非常高效

同类题型扩展

  1. 买卖股票的最佳时机 II(允许多次交易):累加所有上涨区间
  2. 买卖股票的最佳时机 III(最多两次交易):需要二维DP
  3. 买卖股票的最佳时机 IV(最多k次交易):通用DP解法
  4. 含冷冻期/手续费:状态机动态规划

6. 边界情况 / 测试用例 / 变体思考

边界测试用例

  1. 空数组[]→ 返回0
  2. 单元素[5]→ 返回0
  3. 价格一直下跌[7,6,4,3,1]→ 返回0
  4. 价格全相同[3,3,3,3]→ 返回0
  5. 只有两天且盈利[1,2]→ 返回1