文章目录

  • 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.pswp.cn/news/915141.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/915141.shtml
英文地址,请注明出处:http://en.pswp.cn/news/915141.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring MVC @RequestParam注解全解析

RequestParam 注解详解 RequestParam 是 Spring MVC 中最常用的注解之一&#xff0c;用于从 HTTP 请求中提取查询参数&#xff08;Query String&#xff09;或表单数据。它主要处理 application/x-www-form-urlencoded 类型的请求&#xff08;如 GET 请求或 POST 表单提交&…

从零掌握XML与DTD实体:原理、XXE漏洞攻防

本文仅用于技术研究&#xff0c;禁止用于非法用途。 Author:枷锁 文章目录一、XML基础1. 什么是XML&#xff1f;2. XML语法规则3. 数据类型二、DTD1. 认识DTD2. 声明DTD3. DTD实体4. 如何防御XXE攻击&#xff1f;5. 总结一、XML基础 1. 什么是XML&#xff1f; XML &#xff1…

.NET 8 Release Candidate 1 (RC1)现已发布,包括许多针对ASP.NET Core的重要改进!

.NET 8 Release Candidate 1 (RC1)发布&#xff1a;ASP.NET Core重大改进来袭&#xff01; 近日&#xff0c;.NET 8 Release Candidate 1 (RC1)正式发布&#xff0c;这是在今年晚些时候计划发布的最终 .NET 8 版本之前的两个候选版本中的第一个。此版本包含了大部分计划中的功…

Jenkins pipeline 部署docker通用模板

Jenkinsfile: Docker的NETWORK_NAME不要使用bridge默认网络&#xff0c;要使用自定义的网络如test默认 bridge 网络&#xff1a;容器间不能用名字互相访问&#xff0c;只能用 IP。自定义网络&#xff1a;容器间可以用名字互相访问&#xff0c;Docker 自动做了 DNS 解析。pipeli…

【每日算法】专题十五_BFS 解决 FloodFill 算法

1. 算法思想 Flood Fill 问题的核心需求 给定一个二维网格&#xff08;如像素矩阵&#xff09;、一个起始坐标 (x, y) 和目标颜色 newColor&#xff0c;要求&#xff1a; 将起始点 (x, y) 的颜色替换为 newColor。递归地将所有与起始点相邻&#xff08;上下左右&#xff09; …

ESLint 完整功能介绍和完整使用示例演示

以下是ESLint的完整功能介绍和完整使用示例演示&#xff1a; ESLint 完整功能介绍 一、核心功能静态代码分析&#xff1a; 通过解析JavaScript/TypeScript代码为抽象语法树&#xff08;AST&#xff09;&#xff0c;识别语法错误、潜在问题&#xff08;如未定义变量、未使用变量…

解决问题七大步骤

发现问题后寻找解决方案的流程可以细化为 7个核心步骤&#xff0c;每个步骤包含具体措施、信息源和关键技巧&#xff0c;形成“从自查到验证、从独立解决到寻求帮助”的完整闭环。以下是完善后的流程&#xff1a; 一、明确问题与初步自查&#xff08;前提&#xff1a;减少无效搜…

思维链(CoT)技术全景:原理、实现与前沿应用深度解析

一、核心概念与原理 定义与起源 CoT 是一种引导大语言模型&#xff08;LLM&#xff09;显式生成中间推理步骤的技术&#xff0c;通过模拟人类逐步解决问题的过程&#xff0c;提升复杂任务&#xff08;如数学证明、多步逻辑推理&#xff09;的准确性。该概念由 Google Brain 团…

实验-华为综合

华为综合实验 一 实验拓扑二 实验配置交换机2 vlan batch 10 20 int e0/0/2 port link-type access port default vlan 10 int e0/0/1 port link-type access port default vlan 20 int e0/0/3 port link-type trunk port trunk allow-pass vlan alltelnet交换机3 链路类型配置…

Matlab打开慢、加载慢的解决办法

安装完毕后直接打开会非常慢&#xff0c;而且打开了之后还得加载很久才能运行 解决办法如下&#xff1a; 1.找到路径“D:\Program Files\Polyspace\R2020a\licenses”&#xff08;我是把matlab安装在D盘了&#xff0c;如果是其他盘修改路径即可&#xff09;&#xff0c;该路径记…

混沌趋势指标原理及交易展示

1. 引言在金融市场交易中&#xff0c;尤其是加密货币合约交易&#xff0c;趋势跟踪是最主流的策略之一。然而&#xff0c;传统趋势指标如均线、MACD等存在明显的滞后性&#xff0c;往往在趋势确立后才发出信号&#xff0c;导致交易者错失最佳入场时机。更糟糕的是&#xff0c;市…

Java面试宝典:Maven

一、Maven的本质与核心价值 项目管理革命 POM驱动:通过pom.xml文件定义项目结构、依赖、构建规则,实现标准化管理()。示例配置: <dependencies> <dependency> <groupId>org.springframework

可靠消息最终一致性分布式事务解决方案

之前文章写过主流的一些 分布式事务的解决方案&#xff0c;但其实工作中很少有一些高并发的业务中去使用这些方案&#xff0c;因为对于高并发的场景来说&#xff0c;引入这些方案的性能损耗太大&#xff0c;且对系统事务侵入性太强影响系统稳定性。 所以在高并发的业务中&…

ISIS基础

拓扑计算方式 模型 支持的网络 支持的地址OSPF SPF TCP/IP IP网络 IPv4地址ISIS SPF OSI CLNP网络 NSAP地址集成ISIS SPF TCP/IP IP网络 NSAP地址&#xff0c;但可以支持IPv4地址12. …

基于ASP.NET+SQL Server实现(Web)排球赛事网站

排球赛事网的设计与实现摘要随着近几年来计算机技术、网络技术及相应软件技术的迅猛发展&#xff0c;人们的生活已越来越离不开计算机了&#xff0c;而且总是要花费很多时间在它上面。一直以来&#xff0c;排球作为一项大众喜爱的运动&#xff0c;得到广泛传播。随着各项排球赛…

【PTA数据结构 | C语言版】根据后序和中序遍历输出前序遍历

本专栏持续输出数据结构题目集&#xff0c;欢迎订阅。 文章目录题目代码题目 本题要求根据给定的一棵二叉树的后序遍历和中序遍历结果&#xff0c;输出该树的前序遍历结果。 输入格式: 第一行给出正整数 n (≤30)&#xff0c;是树中结点的个数。随后两行&#xff0c;每行给出…

Java HashMap高频面试题深度解析

在 Java 面试中&#xff0c;HashMap 是必问的核心知识点&#xff0c;以下是高频问题和深度解析框架&#xff0c;助你系统性掌握&#xff1a;一、基础概念HashMap 的本质是什么&#xff1f; 基于哈希表的 Map 接口实现&#xff0c;存储键值对&#xff08;Key-Value&#xff09;非…

GitHub Pages无法访问以点号.开头的目录

目录 前言 Jekyll 是什么 启用访问 总结 前言 一些前端项目经常会使用GitHub Pages进行部署展示&#xff0c;但是GitHub Pages 使用的是 Jekyll 引擎&#xff0c;对 Jekyll 引擎不熟悉的小伙伴就会出现如文章标题所言的情况。 Jekyll 是什么 Jekyll 是 GitHub Pages 默认…

JS JSON.stringify介绍(JS序列化、JSON字符串 )(遍历输入值的所有可枚举属性,将其转换为文本表示)缓存序列化、状态管理与时间旅行、replacer

文章目录JSON.stringify 全解析1. 基本概念2. 序列化原理1. 对于原始类型&#xff0c;直接转换为对应的字符串表示2. 对于对象和数组&#xff0c;递归处理其每个属性或元素3. 应用特殊规则处理日期、函数、Symbol 等特殊类型4. 检测并防止循环引用5. 应用 replacer 函数或数组进…

SQLite / LiteDB 单文件数据库为何“清空表后仍占几 GB”?——原理解析与空间回收实战

关键词&#xff1a; SQLite、LiteDB、VACUUM、WAL、auto_vacuum、文件瘦身、数据库维护在嵌入式或桌面、IoT 网关等场景&#xff0c;很多同学都会选择单文件数据库&#xff08;SQLite、LiteDB、SQL CE…&#xff09;。 最近群里一位朋友反馈&#xff1a;“我的 test.db 已经把业…