当前位置: 首页 > news >正文

【华为机试】122. 买卖股票的最佳时机 II

文章目录

  • 122. 买卖股票的最佳时机 II
    • 描述
    • 示例 1
    • 示例 2
    • 示例 3
    • 提示
    • 解题思路
      • 核心观察
      • 关键洞察
    • 算法实现
      • 方法1:贪心算法(推荐)
      • 方法2:动态规划
      • 方法3:动态规划(空间优化)
      • 方法4:波峰波谷法
    • 算法分析
      • 复杂度对比
      • 算法流程图
      • 贪心算法证明
      • 示例分析
    • 动态规划详解
      • 状态转移方程
      • 状态转移图
      • DP状态表(示例)
    • 实际应用
      • 1. 投资策略
      • 2. 类似问题
      • 3. 实际约束
    • 代码实现要点
      • 边界条件处理
      • 数组越界防护
      • 整数溢出考虑
    • 练习建议
    • 相关题目
    • 完整题解代码

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

解题思路

这道题的核心思想是:由于可以进行多次买卖,我们需要抓住每一次股价上涨的机会来获得最大利润

核心观察

  1. 无限制交易:可以进行任意次数的买卖
  2. 同一天可以买卖:先买再卖,或者先卖再买
  3. 最多持有一股:不能同时持有多股
  4. 目标:获得最大总利润

关键洞察

贪心策略:只要明天的价格比今天高,就今天买入明天卖出。这样可以捕获所有的价格上涨段。

算法实现

方法1:贪心算法(推荐)

func maxProfitGreedy(prices []int) int {maxProfit := 0for i := 1; i < len(prices); i++ {if prices[i] > prices[i-1] {maxProfit += prices[i] - prices[i-1]}}return maxProfit
}

原理:累加所有相邻的正收益。

时间复杂度:O(n)
空间复杂度:O(1)

方法2:动态规划

func maxProfitDP(prices []int) int {n := len(prices)dp := make([][2]int, n)dp[0][0] = 0          // 不持有股票dp[0][1] = -prices[0] // 持有股票for i := 1; i < n; i++ {dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i])  // 卖出dp[i][1] = max(dp[i-1][1], dp[i-1][0]-prices[i])  // 买入}return dp[n-1][0]
}

状态定义

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

时间复杂度:O(n)
空间复杂度:O(n)

方法3:动态规划(空间优化)

func maxProfitDPOptimized(prices []int) int {hold := -prices[0]  // 持有股票的最大利润sold := 0           // 不持有股票的最大利润for i := 1; i < len(prices); i++ {newSold := max(sold, hold+prices[i])newHold := max(hold, sold-prices[i])sold, hold = newSold, newHold}return sold
}

时间复杂度:O(n)
空间复杂度:O(1)

方法4:波峰波谷法

找到所有的波谷和波峰,在波谷买入,在波峰卖出。

func maxProfitPeakValley(prices []int) int {i, maxProfit := 0, 0for i < len(prices)-1 {// 找波谷for i < len(prices)-1 && prices[i+1] <= prices[i] {i++}valley := prices[i]// 找波峰for i < len(prices)-1 && prices[i+1] >= prices[i] {i++}peak := prices[i]maxProfit += peak - valley}return maxProfit
}

算法分析

复杂度对比

方法时间复杂度空间复杂度优点缺点
贪心算法O(n)O(1)简洁高效,易理解需要理解贪心思想
动态规划O(n)O(n)状态转移清晰空间开销大
动态规划优化O(n)O(1)空间效率高状态维护复杂
波峰波谷O(n)O(1)直观易懂代码稍复杂
状态机O(n)O(1)逻辑清晰理解成本高

算法流程图

开始
输入价格数组
数组长度 <= 1?
返回0
初始化利润 = 0
遍历第1天到最后一天
今天价格 > 昨天价格?
利润 += 今天价格 - 昨天价格
跳过这一天
还有下一天?
返回总利润
结束

贪心算法证明

命题:对于任意价格序列,贪心策略(累加所有正差值)能够获得最大利润。

证明

  1. 可行性:贪心策略产生的交易序列是合法的(每次买入后必须卖出才能再次买入)
  2. 最优性:任何其他交易策略的利润都不会超过贪心策略

设最优解包含交易 (buy_i, sell_i),其总利润为:

profit = Σ(sell_i - buy_i)

贪心策略的利润为:

greedy_profit = Σ(prices[i] - prices[i-1]) for all i where prices[i] > prices[i-1]

可以证明:greedy_profit >= profit

示例分析

示例1prices = [7,1,5,3,6,4]

贪心分析:
第1天: 7 -> 第2天: 1,下跌,不交易
第2天: 1 -> 第3天: 5,上涨,利润 = 5-1 = 4
第3天: 5 -> 第4天: 3,下跌,不交易  
第4天: 3 -> 第5天: 6,上涨,利润 = 6-3 = 3
第5天: 6 -> 第6天: 4,下跌,不交易总利润 = 4 + 3 = 7

可视化

价格走势图:7 |●|  \5 |   ●|    \3 |     ●---●|          \1 | ●         ●+--+--+--+--+--+1  2  3  4  5  天数交易策略:第2天买入(1) -> 第3天卖出(5):利润4第4天买入(3) -> 第5天卖出(6):利润3

动态规划详解

状态转移方程

dp[i][0] = max(dp[i-1][0], dp[i-1][1] + prices[i])
dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i])

状态转移图

不操作
买入股票
不操作
卖出股票
不持有股票
持有股票

DP状态表(示例)

对于 prices = [7,1,5,3,6,4]

天数价格不持有股票持有股票决策
070-7初始状态
110-1在第1天买入更优
254-1第2天卖出获利4
3341第3天买入
4671第4天卖出获利3
5473保持不持有状态

实际应用

1. 投资策略

  • 短线交易:频繁买卖获取价差
  • 波段操作:在价格波动中获利
  • 算法交易:程序化交易系统

2. 类似问题

  • 买卖股票的最佳时机系列题目
  • 背包问题的变种
  • 区间调度问题

3. 实际约束

在实际投资中还需考虑:

  • 交易手续费
  • 税收影响
  • 市场流动性
  • 价格滑点

代码实现要点

边界条件处理

if len(prices) <= 1 {return 0  // 少于2个价格无法交易
}

数组越界防护

for i := 1; i < len(prices); i++ {  // 从第2个元素开始// 安全访问 prices[i] 和 prices[i-1]
}

整数溢出考虑

对于极大数据,考虑使用 int64 或添加溢出检查。

练习建议

  1. 理解贪心思想:为什么累加所有正差值是最优的?
  2. 掌握状态转换:DP中两个状态如何相互转换?
  3. 空间优化技巧:如何从O(n)优化到O(1)?
  4. 扩展思考:如果有交易手续费怎么办?
  5. 变种练习:限制交易次数的情况如何处理?

相关题目

  • 121. 买卖股票的最佳时机(只能交易一次)
  • 123. 买卖股票的最佳时机 III(最多交易两次)
  • 188. 买卖股票的最佳时机 IV(最多交易k次)
  • 309. 最佳买卖股票时机含冷冻期(含冷冻期)
  • 714. 买卖股票的最佳时机含手续费(含手续费)

完整题解代码

package mainimport ("fmt""time"
)// 方法1:贪心算法 - 抓住每一次上涨机会
// 时间复杂度:O(n),空间复杂度:O(1)
func maxProfitGreedy(prices []int) int {if len(prices) <= 1 {return 0}maxProfit := 0// 只要第二天比第一天价格高,就在第一天买入,第二天卖出for i := 1; i < len(prices); i++ {if prices[i] > prices[i-1] {maxProfit += prices[i] - prices[i-1]}}return maxProfit
}// 方法2:动态规划
// 时间复杂度:O(n),空间复杂度:O(n)
func maxProfitDP(prices []int) int {if len(prices) <= 1 {return 0}n := len(prices)// dp[i][0] 表示第i天不持有股票的最大利润// dp[i][1] 表示第i天持有股票的最大利润dp := make([][2]int, n)// 初始状态dp[0][0] = 0          // 第0天不持有股票,利润为0dp[0][1] = -prices[0] // 第0天买入股票,利润为-prices[0]for i := 1; i < n; i++ {// 第i天不持有股票:可能是前一天就不持有,或者前一天持有今天卖出dp[i][0] = max(dp[i-1][0], dp[i-1][1]+prices[i])// 第i天持有股票:可能是前一天就持有,或者前一天不持有今天买入dp[i][1] = max(dp[i-1][1], dp[i-1][0]-prices[i])}// 最后一天不持有股票肯定比持有股票利润更大return dp[n-1][0]
}// 方法3:动态规划(空间优化)
// 时间复杂度:O(n),空间复杂度:O(1)
func maxProfitDPOptimized(prices []int) int {if len(prices) <= 1 {return 0}// 只需要记录当前的状态,不需要整个数组hold := -prices[0] // 持有股票的最大利润sold := 0          // 不持有股票的最大利润for i := 1; i < len(prices); i++ {newSold := max(sold, hold+prices[i]) // 今天卖出newHold := max(hold, sold-prices[i]) // 今天买入sold = newSoldhold = newHold}return sold
}func max(a, b int) int {if a > b {return a}return b
}func main() {testCases := [][]int{{7, 1, 5, 3, 6, 4}, // 预期输出: 7{1, 2, 3, 4, 5},    // 预期输出: 4{7, 6, 4, 3, 1},    // 预期输出: 0{1, 2, 1, 2, 1},    // 预期输出: 2{5},                // 预期输出: 0{},                 // 预期输出: 0}fmt.Println("=== 买卖股票的最佳时机 II - 多种解法测试 ===")fmt.Println()methods := []struct {name stringfn   func([]int) int}{{"贪心算法", maxProfitGreedy},{"动态规划", maxProfitDP},{"动态规划(优化)", maxProfitDPOptimized},}for i, testCase := range testCases {fmt.Printf("测试用例 %d: %v\n", i+1, testCase)for _, method := range methods {start := time.Now()result := method.fn(testCase)duration := time.Since(start)fmt.Printf("  %s: %d (用时: %v)\n", method.name, result, duration)}fmt.Println()}// 算法思路演示fmt.Println("=== 算法思路演示 ===")demonstrateGreedyApproach([]int{7, 1, 5, 3, 6, 4})
}func demonstrateGreedyApproach(prices []int) {fmt.Printf("\n贪心算法思路演示:\n")fmt.Printf("价格数组: %v\n", prices)maxProfit := 0transactions := []string{}for i := 1; i < len(prices); i++ {if prices[i] > prices[i-1] {profit := prices[i] - prices[i-1]maxProfit += profittransaction := fmt.Sprintf("第%d天买入(%d) -> 第%d天卖出(%d), 利润: %d", i, prices[i-1], i+1, prices[i], profit)transactions = append(transactions, transaction)}}fmt.Println("交易记录:")for _, transaction := range transactions {fmt.Printf("  %s\n", transaction)}fmt.Printf("总利润: %d\n", maxProfit)
} 
http://www.lryc.cn/news/590846.html

相关文章:

  • React 学习(4)
  • 研发知识系统选型实战:从 Notion 到 Gitee Wiki 的迭代经验
  • STM32 DMA通信详解
  • 求解偏微分方程的傅里叶积分解
  • ThreadLocal使用详解-从源码层面分析
  • Python 网络爬虫 —— requests 库和网页源代码
  • 智能体开发工具链全景图:IDE、调试器与监控平台
  • Fair-code介绍(Fair code)(一套新型软件模型:旨在“开源”“商业可持续性”中找到平衡)
  • Windows 11清理C盘方法大全:磁盘清理/禁用休眠/系统还原点/优化大师使用教程
  • Android默认背光亮度配置说明
  • 纯前端html实现图片坐标与尺寸(XY坐标及宽高)获取
  • SegNet:一种用于图像分割的深度卷积编码器解码器架构
  • RocketMQ 高可用集群架构与一致性机制解析
  • 【3D目标检测】Livox Tele-15采集的.lvx数据转.bag再转.pcd
  • 鲍威尔去留风波的AI量化解析:基于多模态数据驱动的金融市场脉冲响应研究
  • 达梦数据守护集群搭建(1主1实时备库1同步备库1异步备库)
  • 时序数据库选型指南 —— 为什么选择 Apache IoTDB?
  • javaweb学习开发代码_HTML-CSS-JS
  • Java面试(基础篇) - 第二篇!
  • slot=“trigger“ 覆盖了组件内部的 ref=“trigger“【详细来龙去脉版 5min】
  • Web开发 01
  • Python的“__name__“属性
  • visual freebasic教程-菜单栏
  • 视频码率是什么?视频流分辨率 2688x1520_25fps采用 h264格式压缩,其码率为
  • 线上协同办公时代:以开源AI大模型等工具培养网感,拥抱职业变革
  • Vim多列打开不同文件操作指南
  • Dijkstra 算法求解多种操作
  • 【真·CPU训模型!】单颗i7家用本,4天0成本跑通中文小模型训练!Xiaothink-T6-mini-Preview 技术预览版开源发布!
  • 腾讯云服务上下载docker以及使用Rabbitmq的流程
  • 闭包的两种设计模式