LeetCode 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。

提示:

  • 1 <= prices.length <= 10^5
  • 0 <= prices[i] <= 10^4

详细问题分析

首先,让我们理解这个问题的关键约束:

  1. 我们只能买入和卖出一次
  2. 必须先买入再卖出(不能先卖出后买入)
  3. 买入和卖出必须在不同的日期

这个问题本质上是寻找数组中的两个元素,使得第二个元素减去第一个元素的差值最大,且第二个元素的索引必须大于第一个元素的索引。

解题思路分析

暴力解法

最直接的方法是使用双重循环,遍历所有可能的买入和卖出组合,找出利润最大的情况。

// 暴力解法 - 不推荐使用,仅用于理解问题
public int maxProfit(int[] prices) {
    int maxProfit = 0;
    for (int i = 0; i < prices.length; i++) {
        for (int j = i + 1; j < prices.length; j++) {
            int profit = prices[j] - prices[i];
            if (profit > maxProfit) {
                maxProfit = profit;
            }
        }
    }
    return maxProfit;
}

但这种方法的时间复杂度是O(n²),对于大规模数据会超时。

一次遍历法(最优解法)

更聪明的方法是利用一个关键洞察:要获得最大利润,我们需要以尽可能低的价格买入,然后在之后尽可能高的价格卖出。

我们可以在遍历数组的同时,记录到目前为止见到的最低价格,并计算如果在这个最低价格买入、当前价格卖出能获得的利润。这样,我们只需要遍历一次数组就能找到最大利润。

详细步骤如下:

  1. 初始化两个变量:minPrice 记录到目前为止的最低价格,maxProfit 记录最大利润
  2. 遍历价格数组:
    1. 如果当前价格低于 minPrice,更新 minPrice
    2. 否则,计算当前利润 = 当前价格 - minPrice,并更新 maxProfit 如果需要

算法执行过程

让我们使用示例 [7,1,5,3,6,4] 详细跟踪算法的执行过程:

初始状态:

  • minPrice = 7 (第一个价格)
  • maxProfit = 0

第2天:价格 = 1

  • 1 < 7,更新 minPrice = 1
  • maxProfit 仍为 0 (没有卖出机会)

第3天:价格 = 5

  • 5 > 1,计算利润 = 5 - 1 = 4
  • 4 > 0,更新 maxProfit = 4

第4天:价格 = 3

  • 3 > 1,计算利润 = 3 - 1 = 2
  • 2 < 4,maxProfit 不变,仍为 4

第5天:价格 = 6

  • 6 > 1,计算利润 = 6 - 1 = 5
  • 5 > 4,更新 maxProfit = 5

第6天:价格 = 4

  • 4 > 1,计算利润 = 4 - 1 = 3
  • 3 < 5,maxProfit 不变,仍为 5

最终结果:maxProfit = 5

示例图表

使用价格折线图和利润变化可以更直观地理解这个问题:

价格走势:

  价格
  ^
7 | *
6 |            *
5 |      *       
4 |                *
3 |          *   
2 |            
1 |    *       
0 +-----------------> 时间
    1  2  3  4  5  6

对应的最低价格和最大利润变化:

Day 1: price = 7, minPrice = 7, maxProfit = 0
Day 2: price = 1, minPrice = 1, maxProfit = 0
Day 3: price = 5, minPrice = 1, maxProfit = 4
Day 4: price = 3, minPrice = 1, maxProfit = 4 (不变)
Day 5: price = 6, minPrice = 1, maxProfit = 5
Day 6: price = 4, minPrice = 1, maxProfit = 5 (不变)

边界情况分析

 1. 空数组或只有一个元素的数组:

        这种情况无法进行买卖交易,应返回0。

2. 价格持续下跌的情况:

        例如 [7,6,4,3,1],最大利润为0,因为没有可以盈利的交易机会。

3. 价格持续上涨的情况:

        例如 [1,2,3,4,5],最大利润为4,应该在第一天买入,最后一天卖出。

详细代码实现与分析

Java 实现

class Solution {
    public int maxProfit(int[] prices) {
        // 边界条件检查:如果数组为空或只有一个元素,无法完成交易,返回0
        if (prices == null || prices.length <= 1) {
            return 0;
        }
        
        // 初始化最低价格为第一天的价格
        int minPrice = prices[0];
        // 初始化最大利润为0(如果无法盈利就返回0)
        int maxProfit = 0;
        
        // 从第二天开始遍历价格数组
        for (int i = 1; i < prices.length; i++) {
            // 情况1:如果当前价格比历史最低价格还低,更新最低价格
            if (prices[i] < minPrice) {
                minPrice = prices[i];
            } 
            // 情况2:如果当前价格高于历史最低价格,计算利润并更新最大利润
            else {
                int currentProfit = prices[i] - minPrice;
                if (currentProfit > maxProfit) {
                    maxProfit = currentProfit;
                }
            }
            
            // 调试信息(实际提交时可以移除)
            // System.out.println("Day " + i + ": price = " + prices[i] + 
            //                    ", minPrice = " + minPrice + ", maxProfit = " + maxProfit);
        }
        
        return maxProfit;
    }
}

代码中的核心思想是维护一个最低价格 minPrice 和一个最大利润 maxProfit。每次遇到比历史最低价格更低的价格时,就更新 minPrice;每当计算出的当前利润比历史最大利润更大时,就更新 maxProfit。

我们也可以用 Math.min() 和 Math.max() 方法简化代码:

class Solution {
    public int maxProfit(int[] prices) {
        // 边界条件检查
        if (prices == null || prices.length <= 1) {
            return 0;
        }
        
        // 初始化最低价格为一个非常大的值,这样第一个价格必定会小于它
        int minPrice = Integer.MAX_VALUE;
        // 初始化最大利润为0
        int maxProfit = 0;
        
        // 遍历价格数组
        for (int price : prices) {
            // 更新最低价格(使用Math.min简化代码)
            minPrice = Math.min(minPrice, price);
            
            // 更新最大利润(使用Math.max简化代码)
            // 注意:即使当前价格小于最低价格,price - minPrice也会是负值
            // 但由于maxProfit的初值为0,不会被负值更新
            maxProfit = Math.max(maxProfit, price - minPrice);
        }
        
        return maxProfit;
    }
}

C# 实现

public class Solution {
    public int MaxProfit(int[] prices) {
        // 边界条件检查:如果数组为空或只有一个元素,无法完成交易,返回0
        if (prices == null || prices.Length <= 1) {
            return 0;
        }
        
        // 初始化变量
        int minPrice = prices[0];  // 历史最低价格
        int maxProfit = 0;         // 最大利润
        
        // 详细跟踪每一天的情况
        Console.WriteLine("初始状态: minPrice = " + minPrice + ", maxProfit = " + maxProfit);
        
        // 从第二天开始遍历
        for (int i = 1; i < prices.Length; i++) {
            int currentPrice = prices[i];
            
            // 检查是否需要更新最低价格
            if (currentPrice < minPrice) {
                minPrice = currentPrice;
                Console.WriteLine("第 " + (i+1) + " 天: 价格 = " + currentPrice + 
                                 ", 更新最低价格为 " + minPrice + ", maxProfit = " + maxProfit);
            } 
            // 检查是否能获得更高的利润
            else {
                int currentProfit = currentPrice - minPrice;
                if (currentProfit > maxProfit) {
                    maxProfit = currentProfit;
                    Console.WriteLine("第 " + (i+1) + " 天: 价格 = " + currentPrice + 
                                     ", minPrice = " + minPrice + ", 更新最大利润为 " + maxProfit);
                } else {
                    Console.WriteLine("第 " + (i+1) + " 天: 价格 = " + currentPrice + 
                                     ", minPrice = " + minPrice + ", maxProfit = " + maxProfit);
                }
            }
        }
        
        return maxProfit;
    }
}

更简洁的C#实现:

public class Solution {
    public int MaxProfit(int[] prices) {
        if (prices == null || prices.Length <= 1) {
            return 0;
        }
        
        int minPrice = int.MaxValue;
        int maxProfit = 0;
        
        foreach (int price in prices) {
            // 更新最低价格
            minPrice = Math.Min(minPrice, price);
            
            // 更新最大利润
            maxProfit = Math.Max(maxProfit, price - minPrice);
        }
        
        return maxProfit;
    }
}

复杂度分析

  • 时间复杂度:O(n),其中 n 是价格数组的长度。我们只需要遍历一次数组。
  • 空间复杂度:O(1),只使用了常数额外空间(minPrice 和 maxProfit 两个变量)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值