题目描述
给定一个数组 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。
算法思路
单次遍历寻找最优买卖点
我们的目标是找到最大利润。利润的计算方式是 卖出价格 - 买入价格。同时,题目明确指出,买入必须发生在卖出之前。
初始想法:暴力枚举
最直接的想法,就是尝试所有可能的买入和卖出组合。
- 确定买入日期:遍历
prices数组,每一天都可以作为买入的日期。 - 确定卖出日期:对于每一个买入日期,再遍历其之后的所有日期,作为可能的卖出日期。
- 计算利润:如果卖出价格高于买入价格,计算利润,并更新最大利润。
这种思路是可行的,但效率不高。如果价格数组很长,我们需要进行两层循环,时间复杂度会达到 O(n^2)。
优化思考:能否只遍历一次?
暴力枚举效率不高,主要原因是做了很多重复的计算:对于每个买入日期,都要遍历后面的所有卖出日期。
关键点在于:只需要找到一个最低的买入价格,以及之后最高的卖出价格。但是,我们不能事先知道哪天是最低买入价,哪天是最高卖出价。
也就是说,目标是找到两个元素 prices[i] 和 prices[j],满足 i < j,并且它们的差值 prices[j] - prices[i] 最大。
实际上,我们不必固定买入日期再去寻找卖出日期,我们可以在遍历价格数组的过程中,同时更新当前的最低买入价格和最大利润。
核心思想:动态维护最低买入价格和最大利润
- 初始化:
maxProfit初始化为0,因为如果没有交易,利润为 0。cost(买入成本) 初始化为prices[0],假设第一天买入,这是一个初始的“最低买入价格”。 当然,更严谨的做法是将cost初始化为一个非常大的数,比如正无穷,但考虑到题目 prices 数组的特性,初始化为prices[0]在本题场景下是可行的,而且更简洁。
- 遍历数组
prices:从头到尾遍历价格数组,对于每一天的价格price:- 计算当前利润: 计算如果今天卖出,并且以之前的
cost买入,能获得的利润price - cost。 - 更新最大利润: 将当前利润与
maxProfit比较,取较大值更新maxProfit。 这样保证maxProfit始终记录遍历到目前为止的最大利润。 - 更新最低买入成本: 将当前价格
price与cost比较,取较小值更新cost。 因为我们希望买入成本尽可能低,所以cost始终记录遍历到目前为止的最低价格。
- 计算当前利润: 计算如果今天卖出,并且以之前的
- 返回结果: 遍历结束后,
maxProfit中存储的就是我们能获得的最大利润。
步骤解析
- 初始化最大利润
maxProfit = 0和最低买入成本cost = prices[0]。 - 遍历价格数组
prices中的每个价格price。 - 计算当前利润:
price - cost。 - 更新最大利润:
maxProfit = max(maxProfit, price - cost)。 - 更新最低买入成本:
cost = min(cost, price)。 - 遍历结束后,返回
maxProfit。
复杂度分析
- 时间复杂度:O(n),只需要一次遍历价格数组。
- 空间复杂度:O(1),只使用了常数个额外变量 (
maxProfit,cost)。
代码实现
func maxProfit(prices []int) int {
maxProfit := 0
cost := prices[0] // 初始化买入成本为第一天的价格
for _, price := range prices {
maxProfit = max(maxProfit, price-cost) // 更新最大利润
cost = min(cost, price) // 更新最低买入成本
}
return maxProfit
}
示例解析
以 prices = [7,1,5,3,6,4] 为例:
-
初始状态:
maxProfit = 0,cost = 7。 -
遍历过程:
- 第1天 (价格 7):
price = 7。maxProfit = max(0, 7-7) = 0,cost = min(7, 7) = 7。 - 第2天 (价格 1):
price = 1。maxProfit = max(0, 1-7) = 0,cost = min(7, 1) = 1。 - 第3天 (价格 5):
price = 5。maxProfit = max(0, 5-1) = 4,cost = min(1, 5) = 1。 - 第4天 (价格 3):
price = 3。maxProfit = max(4, 3-1) = 4,cost = min(1, 3) = 1。 - 第5天 (价格 6):
price = 6。maxProfit = max(4, 6-1) = 5,cost = min(1, 6) = 1。 - 第6天 (价格 4):
price = 4。maxProfit = max(5, 4-1) = 5,cost = min(1, 4) = 1。
- 第1天 (价格 7):
-
最终结果:
maxProfit = 5。
关键点
- 一次遍历完成: 巧妙地使用单次遍历,动态更新最低买入成本和最大利润,将时间复杂度降到 O(n)。
- 贪心思想: 每一步都做出当前看来最优的选择(保持最低买入成本,更新最大利润),最终得到全局最优解。
- 理解
cost的作用:cost变量至关重要,它记录了遍历到当前天数为止的最低价格,为后续计算利润提供了基准。