LeetCode 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。
最大总利润为 4 + 3 = 7 。

示例 2:

输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4。
最大总利润为 4 。

示例 3:

输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 交易无法获得正利润,所以不参与交易可以获得最大利润,最大利润为 0。

提示:

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

 问题分析

与"买卖股票的最佳时机 I"不同,这道题允许我们进行多次交易,但有以下限制:

  1. 在任何时候最多只能持有一股股票
  2. 必须先买入才能卖出(不能卖空)
  3. 可以在同一天买入并卖出

关键问题是:如何安排买入和卖出的时机,使得总利润最大?


解题思路

贪心算法

这道题有一个非常直观的贪心解法。由于可以进行无限次交易,我们可以采取"只要有利可图就交易"的策略。具体来说:

  1. 当股票价格上涨时(明天价格 > 今天价格),我们应该在今天买入,明天卖出,赚取差价
  1. 当股票价格下跌时,我们不进行任何操作

这个策略可以进一步简化:只需要将所有相邻两天的价格上涨都累加起来,就得到了最大利润。

例如,对于数组 [1,2,3,4,5]:

  • 第1天买入,第5天卖出,利润是 5-1=4
  • 等价于:(2-1)+(3-2)+(4-3)+(5-4)=4

即将所有上涨的部分都累加起来,就是最大利润。

为什么贪心算法有效?

假设我们有三天的价格:a, b, c,其中 a < b > c(即价格先上涨后下跌)。

如果我们在价格为a时买入,在价格为b时卖出,然后在价格为c时再买入,总利润为(b-a)。

如果我们选择持有(即在价格为a时买入,一直持有到价格为c),利润为(c-a),由于c<b,所以(c-a)<(b-a),不如前一种策略获利多。

代码实现

Java 实现

class Solution {
    public int maxProfit(int[] prices) {
        // 边界条件检查
        if (prices == null || prices.length <= 1) {
            return 0;
        }
        
        int totalProfit = 0;
        
        // 遍历价格数组
        for (int i = 1; i < prices.length; i++) {
            // 如果当前价格高于前一天的价格,就进行交易
            if (prices[i] > prices[i - 1]) {
                // 累加这一天的利润
                totalProfit += prices[i] - prices[i - 1];
            }
        }
        
        return totalProfit;
    }
}

我们还可以添加一些调试信息,以便更清楚地理解算法的执行过程:

class Solution {
    public int maxProfit(int[] prices) {
        if (prices == null || prices.length <= 1) {
            return 0;
        }
        
        int totalProfit = 0;
        System.out.println("初始总利润: " + totalProfit);
        
        for (int i = 1; i < prices.length; i++) {
            int todayPrice = prices[i];
            int yesterdayPrice = prices[i - 1];
            
            if (todayPrice > yesterdayPrice) {
                int profit = todayPrice - yesterdayPrice;
                totalProfit += profit;
                System.out.println("第" + i + "天: 价格从 " + yesterdayPrice + 
                                  " 上涨到 " + todayPrice + ", 获利 " + profit + 
                                  ", 总利润增加到 " + totalProfit);
            } else {
                System.out.println("第" + i + "天: 价格从 " + yesterdayPrice + 
                                  " 下跌到 " + todayPrice + ", 不操作, 总利润仍为 " + 
                                  totalProfit);
            }
        }
        
        return totalProfit;
    }
}

输入:prices = [7,1,5,3,6,4]

初始总利润: 0
第1天: 价格从 7 下跌到 1, 不操作, 总利润仍为 0
第2天: 价格从 1 上涨到 5, 获利 4, 总利润增加到 4
第3天: 价格从 5 下跌到 3, 不操作, 总利润仍为 4
第4天: 价格从 3 上涨到 6, 获利 3, 总利润增加到 7
第5天: 价格从 6 下跌到 4, 不操作, 总利润仍为 7

C# 实现

public class Solution {
    public int MaxProfit(int[] prices) {
        // 边界条件检查
        if (prices == null || prices.Length <= 1) {
            return 0;
        }
        
        int totalProfit = 0;
        
        // 遍历价格数组
        for (int i = 1; i < prices.Length; i++) {
            // 如果当前价格高于前一天的价格,就进行交易
            if (prices[i] > prices[i - 1]) {
                // 累加这一天的利润
                totalProfit += prices[i] - prices[i - 1];
            }
        }
        
        return totalProfit;
    }
}

详细执行过程图解

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

初始总利润:0

第1天 → 第2天:价格从 7 下跌到 1,不操作,总利润仍为 0

第2天 → 第3天:价格从 1 上涨到 5,进行交易:

  • 第2天买入(价格=1)
  • 第3天卖出(价格=5)
  • 获利:5-1=4
  • 总利润:0+4=4

第3天 → 第4天:价格从 5 下跌到 3,不操作,总利润仍为 4

第4天 → 第5天:价格从 3 上涨到 6,进行交易:

  • 第4天买入(价格=3)
  • 第5天卖出(价格=6)
  • 获利:6-3=3
  • 总利润:4+3=7

第5天 → 第6天:价格从 6 下跌到 4,不操作,总利润仍为 7

最终总利润:7

价格-时间图解

股票价格变化图:

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

交易策略图解:

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

算法分析

贪心算法的正确性

为什么这个贪心策略是正确的?我们可以证明,对于任何一段连续上涨的区间[i, j],在i买入,在j卖出的利润等于区间内所有相邻两天价格差的总和:

prices[j] - prices[i] = (prices[i+1] - prices[i]) + (prices[i+2] - prices[i+1]) + ... + (prices[j] - prices[j-1])

因此,无论是选择在整个上涨区间买卖一次,还是每天都进行买卖,最终的利润是相同的。而对于下跌区间,不进行操作是最优的。

复杂度分析

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

动态规划解法

虽然贪心算法已经能够解决这个问题,但我们也可以使用动态规划的思路来解决。这种方法对于理解更复杂的股票问题很有帮助。

定义状态:

  • dp[i][0]:第i天交易结束后不持有股票的最大利润
  • dp[i][1]:第i天交易结束后持有股票的最大利润

状态转移方程:

  • dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])(今天不持有 = max(昨天不持有,昨天持有今天卖出))
  • dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i])(今天持有 = max(昨天持有,昨天不持有今天买入))

初始状态:

  • dp[0][0] = 0(第0天不持有股票,利润为0)
  • dp[0][1] = -prices[0](第0天持有股票,利润为-prices[0])

最终答案是 dp[n-1][0],即最后一天不持有股票的最大利润。

Java实现:

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        if (n <= 1) return 0;
        
        int[][] dp = new int[n][2];
        
        // 初始状态
        dp[0][0] = 0;            // 第0天不持有股票
        dp[0][1] = -prices[0];   // 第0天持有股票
        
        // 状态转移
        for (int i = 1; i < n; i++) {
            // 今天不持有 = max(昨天不持有,昨天持有今天卖出)
            dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i]); 
            // 今天持有 = max(昨天持有,昨天不持有今天买入)
            dp[i][1] = Math.max(dp[i-1][1], dp[i-1][0] - prices[i]);
        }
        
        return dp[n-1][0];  // 最后一天不持有股票的最大利润
    }
}

空间优化的动态规划(因为只依赖前一天的状态):

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        if (n <= 1) return 0;
        
        int noStock = 0;           // 不持有股票的最大利润
        int hasStock = -prices[0]; // 持有股票的最大利润
        
        for (int i = 1; i < n; i++) {
            int prevNoStock = noStock;
            // 今天不持有 = max(昨天不持有,昨天持有今天卖出)
            noStock = Math.max(noStock, hasStock + prices[i]);
            // 今天持有 = max(昨天持有,昨天不持有今天买入)
            hasStock = Math.max(hasStock, prevNoStock - prices[i]);
        }
         
        return noStock;
    }
}

总结

买卖股票的最佳时机 II 是一个经典的贪心算法问题。通过简单的策略——只要价格上涨就进行交易,我们就能获得最大的利润。

这个问题的关键洞察是:

  1. 由于可以进行无限次交易,我们可以将任何一段上涨区间的总利润分解为每日差价之和
  2. 对于下跌区间,不进行操作是最优的

与第一题(只能交易一次)相比,这个问题实际上更简单,因为我们不再需要寻找全局最优的买入卖出点,而是可以采取局部贪心的策略。

理解这个问题对于解决更复杂的股票交易问题(如限制交易次数、有冷却期、有交易费用等)也很有帮助。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值