Python 基础算法——买卖股票的最佳时机

16. 买卖股票的最佳时机

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择某一天买入这只股票,并选择在未来的某一个不同的日子卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

示例:

输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。

常用的算法分别是:一次遍历法快慢指针法暴力法 分治法

 一次遍历法:

使用一次遍历求最大股票利润的代码的思路:

  1. 初始化最小价格 min_price 为正无穷大,最大利润 max_profit 为 0
  2. 遍历价格数组 prices,对每个价格 price:
  3. 更新 min_price,使其始终记录到当前为止遇到的最小价格
  4. 计算当前价格减去最小价格的利润 profit
  5. 更新 max_profit,使其维护最大利润
  6. 遍历完成后,max_profit 即为最大利润
# 一次遍历法    时间复杂度:O(n)    空间复杂度:O(1)
def maxProfit1(prices):
    min_price = float('inf')
    max_profit = 0

    for price in prices:
        min_price = min(min_price, price)
        profit = price - min_price
        max_profit = max(max_profit, profit)
    
    return max_profit

时间复杂度:O(n),只需要遍历一次价格数组,进行最小价格和最大利润的更新计算,所以时间复杂度为线性复杂度O(n)。

空间复杂度:O(1),只需要两个变量min_price和max_profit来保存中间结果,空间复杂度为常数阶O(1)。

快慢指针法:

两种快慢指针解法思路都是正确的:

  1. 快慢指针法1:
  • 使用while循环遍历,两个指针i, j
  • 根据价格比较情况,更新左指针
  • 计算利润,更新最大利润
  1. 快慢指针法2:
  • 使用for循环,一个指针i,,一个索引j
  • 比较价格更新左指针
  • 计算利润,更新最大利润

两种实现思路非常相似,主要不同在于:

  • 第一种使用双指针i,j和while循环
  • 第二种简化为单指针i和for循环
# 快慢指针法1    时间复杂度:O(n)    空间复杂度:O(1)
def maxProfit2(prices):
    profit = 0
    i, j = 0, 1
    if len(prices) <= j:
        return 0
    
    left, right = prices[i], prices[j]

    while True:
        if right < left:
            left = right
            i = j
        else:
            profit = max(profit, right - left)
        
        left = prices[i]
        j += 1

        if j >= len(prices):
            break
        else:
            right = prices[j]

    return profit

# 快慢指针法2    时间复杂度:O(n)    空间复杂度:O(1)
def maxProfit2_1(prices):
    profit = 0
    i = 0
    
    for j in range(1, len(prices)): # 用for循环遍历数组
        if prices[j] < prices[i]: # 如果右边的元素小于左边的元素
            i = j # 更新左边的指针
        else: # 否则
            profit = max(profit, prices[j] - prices[i]) # 更新利润
    
    return profit

时间复杂度:O(n),快慢指针需要线性遍历数组一次,所以时间复杂度为 O(n)。

空间复杂度:O(1),只需要几个变量存放指针和中间结果,所以空间复杂度为 O(1) 的常数复杂度。

暴力法:

使用暴力法求解最大股票利润的代码思路是:

  1. 使用双层for循环枚举所有买入和卖出的组合
  2. 计算每种组合的利润profit
  3. 更新最大利润max_profit
# 暴力法    时间复杂度:O(n^2)    空间复杂度:O(1)
def maxProfit3(prices):
    max_profit = 0
    for i in range(len(prices)):
        for j in range(i+1, len(prices)):
            profit = prices[j] - prices[i]
            max_profit = max(max_profit, profit)

    return max_profit

时间复杂度:O(N^2),外层循环为N次,内层循环也为N次,所以总体复杂度是N^2。

空间复杂度:O(1),只使用了两个变量max_profit和profit,占用常数大小的额外空间。

差分法:

使用差分法求最大股票利润的思路:

  1. 计算价格数组的差分(对应两天的价格差值)
  2. 将差分累加可以得到截止到当前天的总利润
  3. 如果累加利润小于0,则重置为0(处理价格下跌情况)
  4. 比较当前累加利润和之前最大利润,实时维护最大利润
  5. 遍历完成后,最大利润即所求
# 差分法    时间复杂度:O(n)    空间复杂度:O(1)
def maxProfit4(prices):
    # 初始化最大利润、初始化当前利润
    max_profit = 0
    cur_profit = 0
    for i in range(1, len(prices)):
        # 计算差分
        diff = prices[i] - prices[i-1]
        # 更新当前利润,如果小于0,则设置为0
        cur_profit = max(cur_profit+diff, 0)
        # 更新最大利润
        max_profit = max(max_profit, cur_profit)
    return max_profit

时间复杂度: O(n),需要线性遍历数组一次

空间复杂度: O(1):,只使用了两个变量

分治法:

分治法求最大股票利润算法的思路:

  1. 定义递归函数helper,以区间[left, right]为参数
  2. 如果区间只有1个或0个元素,最大利润为0
  3. 找到区间的中点mid,递归求解左右两区间
  4. 计算跨越mid的最大利润:找到左区间最大值、右区间最小值
  5. 返回三者最大值作为该区间的最大利润
# 分治法    时间复杂度:O(nlogn)    空间复杂度:O(log(n))
def maxProfit5(prices): # 定义函数maxProfit5,接受一个数组prices作为参数
    # 定义辅助函数helper,求解区间[left, right]内的最大利润
    def helper(left, right):
        # 如果区间长度小于等于1,则返回0
        if left >= right:
            return 0
        # 计算中点位置,左/右半部分的最大利润
        mid = (left + right) // 2 # 使用整除运算符//来计算中点位置
        left_max = helper(left, mid) # 递归调用helper函数,求解左半部分[left, mid]的最大利润
        right_max = helper(mid+1, right) # 递归调用helper函数,求解右半部分[mid+1, right]的最大利润
        # 求解跨越中点的最大利润,即找到左半部分的最低价格和右半部分的最高价格
        min_price = prices[left] # 初始化最低价格为区间左端点的价格
        for i in range(left+1, mid+1): # 遍历左半部分[left+1, mid]的元素
            min_price = min(min_price, prices[i]) # 更新最低价格为当前价格和之前最低价格中的较小值
        
        max_price = prices[mid + 1] # 初始化最高价格为区间右端点的价格

        for i in range(mid+2, right+1): # 遍历右半部分[mid+2, right]的元素
            max_price = max(max_price, prices[i]) # 更新最高价格为当前价格和之前最高价格中的较大值
        
        # 计算跨越中点的最大利润
        cross_max = max_price - min_price # 使用最高价格减去最低价格得到跨越中点的最大利润
        # 返回三者中的最大值
        return max(left_max, right_max, cross_max) # 使用max函数返回左半部分、右半部分和跨越中点三者中的最大值作为区间[left, right]内的最大利润
    # 调用辅助函数helper,求解整个数组的最大利润
    return helper(0, len(prices)-1) # 传入参数0和len(prices)-1,表示整个数组的区间[0, len(prices)-1]

 时间复杂度:每次将区间分成两个子区间,然后递归求解它们,直到区间长度小于等于1为止。这样,每一层递归都需要遍历整个数组,所以每一层递归的时间复杂度是O(n),其中n是数组的长度。而递归的深度是log(n),因为每次区间长度减半。所以总的时间复杂度是O(n)乘以O(log(n)),即O(nlog(n))。

空间复杂度:只需要常数个额外变量来存储最低价格、最高价格、中点位置等信息,所以额外空间复杂度是O(1)。而递归调用会消耗栈空间,栈空间与递归深度成正比,所以栈空间复杂度是O(log(n))。所以总的空间复杂度是O(1)加上O(log(n)),即O(log(n))。

题目来源:力扣

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值