17. 买卖股票的最佳时机 II
给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。
返回 你能获得的 最大 利润 。
示例:
输入: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 。
常用的算法分别是:贪心算法、 动态规划 和 谷峰法 。
贪心算法:
它通过遍历整个股票价格数组,寻找所有的递增子序列,并计算每个递增子序列对应的利润,
并将所有利润相加得到最终的最大利润。
代码思路:
- 初始化变量
profit
为0,表示当前的最大利润。 - 对于
prices
数组中的每一天的股票价格,从第二天开始遍历。 - 如果当前股票价格比前一天的价格高,说明可以在前一天买入并在当前天卖出,从而获得利润,将这部分利润加到
profit
变量中。 - 最后返回
profit
即为最大利润。
# 贪心算法 时间复杂度:O(n) 空间复杂度:O(1)
def maxProfit1(prices):
profit = 0
# 特殊情况:数组个数为0/1
if len(prices) <= 1:
return profit
for i in range(1, len(prices)):
if prices[i] > prices[i-1]:
profit += prices[i] - prices[i-1]
return profit
时间复杂度: 代码中使用一个循环来遍历整个 prices
数组,对于每个股票价格进行了常数时间的操作。因此,时间复杂度为O(n),其中 n 是 prices
数组的大小。
空间复杂度: 代码只使用了一个额外变量 profit
来存储最大利润,而不需要使用额外的数据结构或数组。因此,空间复杂度为O(1),是一个常数级别的空间消耗。
动态规划1:
它通过构建一个动态规划表格来解决买卖股票的最佳时机问题。在每一天,我们根据两种选择(买入或卖出股票)来更新动态规划表格,然后利用中间结果的保存和复用,避免了重复计算,最终得到能获得的最大利润。
代码思路:
- 初始化一个大小为n的动态规划表格
dp
,其中dp[i]
表示第i天结束时的最大利润。 - 对于
prices
数组中的每一天的股票价格,从第二天开始遍历。 - 如果今天的股票价格比昨天高,说明可以在昨天买入并在今天卖出,从而获得利润。因此,
dp[i]
更新为上一次持有股票的最大利润加上今天的利润(即dp[i-1] + prices[i] - prices[i-1]
)。 - 如果今天的股票价格不比昨天高,说明不能卖出,所以
dp[i]
维持为上一次的最大利润(即dp[i-1]
)。 - 最后返回
dp[-1]
即为最大利润。
# 动态规划1 时间复杂度:O(n) 空间复杂度:O(n)
def max_profit2(prices):
n = len(prices)
# 特殊情况:数组个数为0/1
if n <= 1:
return 0
# 初始化动态规划表格
dp = [0] * n
for i in range(1, n):
# 如果股票价格上涨,则卖出股票,计算利润
if prices[i] > prices[i-1]:
dp[i] = dp[i-1] + prices[i] - prices[i-1]
else:
dp[i] = dp[i-1]
return dp[-1]
时间复杂度: 代码中使用了一个循环来遍历整个 prices
数组,对于每个股票价格进行了常数时间的操作。因此,时间复杂度为O(n),其中 n 是 prices
数组的大小。
空间复杂度: 代码使用了一个大小为n的动态规划表格 dp
来存储中间结果,需要额外的空间来保存这个表格。因此,空间复杂度为O(n),与输入数组的大小成线性关系。
动态规划2:
它通过使用两个变量 f0
和 f1
来存储中间结果,以实现对最大利润的更新。代码通过一次遍历 prices
数组来更新 f0
和 f1
的值,最后返回 f0
即为最大利润。
代码思路:
-
首先,初始化两个变量
f0
和f1
,分别表示两个阶段的最大利润。f0
表示未持有股票时的最大利润,而f1
表示持有股票时的最大利润。 -
对于
prices
数组中的每一天的股票价格,从头到尾进行遍历。 -
对于第 i 天,我们有两种选择:
- 如果今天不持有股票,则
f0
可能是昨天不持有股票的利润f0
,或者昨天持有股票并在今天卖出的利润f1 + p
中的较大值。即f0 = max(f0, f1 + p)
。这个操作相当于在前一天不持有股票时保持不变,或者在前一天持有股票并在今天卖出获得利润。 - 如果今天持有股票,则
f1
可能是昨天持有股票的利润f1
,或者昨天不持有股票并在今天买入的利润f0 - p
中的较大值。即f1 = max(f1, f0 - p)
。这个操作相当于在前一天持有股票时保持不变,或者在前一天不持有股票并在今天买入获得利润。
- 如果今天不持有股票,则
-
最后返回
f0
,即为最大利润。
# 动态规划2(超简化版) 时间复杂度:O(n) 空间复杂度:O(1)
def maxProfit(prices):
f0, f1 = 0, -inf
for p in prices:
f0, f1 = max(f0, f1 + p), max(f1, f0 - p)
return f0
时间复杂度: O(n),因为它只对 prices
数组进行了一次遍历。
空间复杂度:O(1),因为只使用了两个额外的变量 f0
和 f1
来存储中间结果,没有使用额外的数据结构或数组。
峰谷法:
它通过利用峰谷之间的差值来计算最大利润。
代码思路:
-
首先,初始化一个变量
total_profit
为0,用于累积总利润。 -
对于
prices
数组中的每一天的股票价格,使用一个循环从头到尾进行遍历。 -
在循环中,首先寻找谷值:即找到一个价格
valley
,满足在之后的价格中是一个相对低点(之后的价格递增)。 -
然后,寻找峰值:即找到一个价格
peak
,满足在之后的价格中是一个相对高点(之后的价格递减)。 -
计算峰谷差值
peak - valley
,并将其累加到total_profit
中。 -
重复步骤3到步骤5,直到遍历完整个
prices
数组。 -
最后返回
total_profit
,即为最大利润。
# 峰谷法 时间复杂度:O(n) 空间复杂度:O(1)
def max_profit3(prices):
n = len(prices)
if n <= 1:
return 0
total_profit = 0
i = 0
while i < n -1:
# 寻找谷值
while i < n -1 and prices[i] >= prices[i+1]:
i += 1
valley = prices[i]
# 寻找峰值
while i < n - 1 and prices[i] <= prices[i+1]:
i += 1
peak = prices[i]
# 计算峰谷差值,并累加到总利润
total_profit += peak - valley
return total_profit
时间复杂度:O(n),其中 n 是 prices
数组的大小。算法通过一次遍历 prices
数组来寻找峰值和谷值,并计算峰谷差值,因此时间复杂度为线性级别。
空间复杂度:O(1)。算法只使用了常数个额外变量来存储中间结果,而不需要使用额外的数据结构或数组。因此,空间复杂度是常数级别的,与输入数组的大小无关。