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

【计算机算法设计与分析】动态规划与贪心算法教程:从矩阵连乘到资源优化

文章目录

    • 一、算法设计范式概览
      • 动态规划 vs 贪心算法
    • 二、问题一:矩阵连乘问题(动态规划经典案例)
      • 问题描述
      • 为什么用动态规划?
      • 动态规划算法设计
        • 1. 状态定义
        • 2. 状态转移方程
        • 3. 填表过程(自底而上)
        • 4. 结果与回溯
        • 5. 复杂度分析
      • 关键洞察
    • 三、问题二:钢条切割问题(动态规划简化版)
      • 问题描述
      • 问题特性
      • 动态规划算法设计
        • 1. 状态定义
        • 2. 状态转移方程
        • 3. 填表过程(n=6)
        • 4. 结果
        • 5. 复杂度分析
      • 与矩阵连乘的对比
    • 四、问题三:苹果买卖问题(贪心算法案例)
      • 问题描述
      • 为什么用贪心算法?
      • 贪心算法设计
        • 1. 核心思路
        • 2. 算法伪代码
        • 3. 算法执行示例
        • 4. 正确性证明
        • 5. 复杂度分析
      • 与动态规划的对比
    • 五、三种问题的算法选择决策树
      • 问题特征对比表
    • 六、总结:算法选择的决策框架
      • 核心原则
      • 性能对比

本文通过三个经典问题——矩阵连乘、钢条切割、苹果买卖,深入解析动态规划和贪心算法的核心思想、设计逻辑和应用场景,帮助读者掌握这两种重要的算法设计范式。

一、算法设计范式概览

动态规划 vs 贪心算法

共同点

  • 都用于解决优化问题
  • 都利用最优子结构特性:原问题的最优解由子问题的最优解组成
  • 都需要将大问题分解为子问题

核心区别

  • 动态规划:需要枚举所有可能的分解情况,通过"查表"避免重复计算,通常需要解决所有子问题
  • 贪心算法:通过"贪心选择特性",每一步只做局部最优决策,不需要枚举所有可能

选择原则

  • 如果问题满足贪心选择特性(局部最优能保证全局最优),优先用贪心算法(通常更简单高效)
  • 如果只满足最优子结构但不满足贪心选择特性,用动态规划

二、问题一:矩阵连乘问题(动态规划经典案例)

问题描述

给定5个矩阵A、B、C、D、E,维度分别为:

  • A: 6×11
  • B: 11×7
  • C: 7×15
  • D: 15×3
  • E: 3×21

目标:找到最优连乘顺序,使总乘法次数最少。

为什么用动态规划?

问题特性分析

  1. 最优子结构:如果 A₁...Aₙ 的最优顺序是 (A₁...Aₖ)(Aₖ₊₁...Aₙ),那么两个子序列也必须是各自的最优顺序
  2. 重叠子问题(A₂A₃A₄) 既是 (A₂A₃A₄A₅) 的子问题,也是 (A₁A₂A₃A₄) 的子问题
  3. 不满足贪心选择:无法通过"局部最优"直接确定全局最优,必须枚举所有切割点

穷举法不可行

  • n个矩阵的加括号方式数量是卡塔兰数,指数级增长
  • n=5时有14种方式,n=10时有4862种,n=20时超过10⁹种

动态规划算法设计

1. 状态定义

OPT(i, j) = 计算矩阵链 AᵢAᵢ₊₁...Aⱼ 所需的最少乘法次数

2. 状态转移方程
OPT(i, j) = min{OPT(i, k) + OPT(k+1, j) + pᵢ₋₁pₖpⱼ}  (i ≤ k ≤ j-1)

逻辑解释

  • 任何连乘顺序都可以看作"先连乘左边,再连乘右边,最后合并"
  • 枚举所有可能的切割点k,选择总代价最小的
  • pᵢ₋₁pₖpⱼ 是合并两个子矩阵的代价
3. 填表过程(自底而上)

维度序列:p = [6, 11, 7, 15, 3, 21]

步骤1:初始化

  • OPT[i, i] = 0(单个矩阵不需要乘法)

步骤2:长度为2的链

  • OPT[1,2] = 6×11×7 = 462
  • OPT[2,3] = 11×7×15 = 1155
  • OPT[3,4] = 7×15×3 = 315
  • OPT[4,5] = 15×3×21 = 945

步骤3:长度为3的链

计算 OPT[1,3](矩阵链 A₁A₂A₃):

  • 切割点 k=1:(A₁)(A₂A₃)
    • OPT[1,1] + OPT[2,3] + p₀×p₁×p₃ = 0 + 1155 + 6×11×15 = 0 + 1155 + 990 = 2145
  • 切割点 k=2:(A₁A₂)(A₃)
    • OPT[1,2] + OPT[3,3] + p₀×p₂×p₃ = 462 + 0 + 6×7×15 = 462 + 0 + 630 = 1092 ✓(最小)
  • OPT[1,3] = min{2145, 1092} = 1092K[1,3] = 2

计算 OPT[2,4](矩阵链 A₂A₃A₄):

  • 切割点 k=2:(A₂)(A₃A₄)
    • OPT[2,2] + OPT[3,4] + p₁×p₂×p₄ = 0 + 315 + 11×7×3 = 0 + 315 + 231 = 546 ✓(最小)
  • 切割点 k=3:(A₂A₃)(A₄)
    • OPT[2,3] + OPT[4,4] + p₁×p₃×p₄ = 1155 + 0 + 11×15×3 = 1155 + 0 + 495 = 1650
  • OPT[2,4] = min{546, 1650} = 546K[2,4] = 2

计算 OPT[3,5](矩阵链 A₃A₄A₅):

  • 切割点 k=3:(A₃)(A₄A₅)
    • OPT[3,3] + OPT[4,5] + p₂×p₃×p₅ = 0 + 945 + 7×15×21 = 0 + 945 + 2205 = 3150
  • 切割点 k=4:(A₃A₄)(A₅)
    • OPT[3,4] + OPT[5,5] + p₂×p₄×p₅ = 315 + 0 + 7×3×21 = 315 + 0 + 441 = 756 ✓(最小)
  • OPT[3,5] = min{3150, 756} = 756K[3,5] = 4

步骤4:长度为4的链

计算 OPT[1,4](矩阵链 A₁A₂A₃A₄):

  • 切割点 k=1:(A₁)(A₂A₃A₄)
    • OPT[1,1] + OPT[2,4] + p₀×p₁×p₄ = 0 + 546 + 6×11×3 = 0 + 546 + 198 = 744 ✓(最小)
  • 切割点 k=2:(A₁A₂)(A₃A₄)
    • OPT[1,2] + OPT[3,4] + p₀×p₂×p₄ = 462 + 315 + 6×7×3 = 462 + 315 + 126 = 903
  • 切割点 k=3:(A₁A₂A₃)(A₄)
    • OPT[1,3] + OPT[4,4] + p₀×p₃×p₄ = 1092 + 0 + 6×15×3 = 1092 + 0 + 270 = 1362
  • OPT[1,4] = min{744, 903, 1362} = 744K[1,4] = 1

计算 OPT[2,5](矩阵链 A₂A₃A₄A₅):

  • 切割点 k=2:(A₂)(A₃A₄A₅)
    • OPT[2,2] + OPT[3,5] + p₁×p₂×p₅ = 0 + 756 + 11×7×21 = 0 + 756 + 1617 = 2373
  • 切割点 k=3:(A₂A₃)(A₄A₅)
    • OPT[2,3] + OPT[4,5] + p₁×p₃×p₅ = 1155 + 945 + 11×15×21 = 1155 + 945 + 3465 = 5565
  • 切割点 k=4:(A₂A₃A₄)(A₅)
    • OPT[2,4] + OPT[5,5] + p₁×p₄×p₅ = 546 + 0 + 11×3×21 = 546 + 0 + 693 = 1239 ✓(最小)
  • OPT[2,5] = min{2373, 5565, 1239} = 1239K[2,5] = 4

步骤5:长度为5的链(最终结果)

计算 OPT[1,5](矩阵链 A₁A₂A₃A₄A₅):

  • 切割点 k=1:(A₁)(A₂A₃A₄A₅)
    • OPT[1,1] + OPT[2,5] + p₀×p₁×p₅ = 0 + 1239 + 6×11×21 = 0 + 1239 + 1386 = 2625
  • 切割点 k=2:(A₁A₂)(A₃A₄A₅)
    • OPT[1,2] + OPT[3,5] + p₀×p₂×p₅ = 462 + 756 + 6×7×21 = 462 + 756 + 882 = 2100
  • 切割点 k=3:(A₁A₂A₃)(A₄A₅)
    • OPT[1,3] + OPT[4,5] + p₀×p₃×p₅ = 1092 + 945 + 6×15×21 = 1092 + 945 + 1890 = 3927
  • 切割点 k=4:(A₁A₂A₃A₄)(A₅)
    • OPT[1,4] + OPT[5,5] + p₀×p₄×p₅ = 744 + 0 + 6×3×21 = 744 + 0 + 378 = 1122 ✓(最小)
  • OPT[1,5] = min{2625, 2100, 3927, 1122} = 1122K[1,5] = 4
4. 结果与回溯

最优连乘次数:1122

最优连乘顺序:根据K表回溯得到 ((A(B(CD)))E)

验证:按此顺序计算

  1. C×D:7×15×3 = 315
  2. B×(CD):11×7×3 = 231
  3. A×(B(CD)):6×11×3 = 198
  4. (A(B(CD)))×E:6×3×21 = 378
    总计:315 + 231 + 198 + 378 = 1122 ✓
5. 复杂度分析
  • 时间复杂度:O(n³)(三重循环)
  • 空间复杂度:O(n²)(存储OPT表和K表)

关键洞察

  1. 中间结果维度很重要:尽量让中间结果维度小,后续计算更省
  2. 自底而上填表:确保计算 OPT(i, j) 时,所有子问题都已计算完成
  3. 枚举所有切割点:因为不知道哪个切割点最优,必须全部枚举
  4. 最优子结构:全局最优解必须由局部最优解组成

三、问题二:钢条切割问题(动态规划简化版)

问题描述

给定一根长度为n英寸的钢条,将其切割成整数长度的段,每段可以单独出售。价格表如下:

长度(英寸)123456
价格(元)144789

目标:设计动态规划算法,使总收益最大化。

问题特性

  1. 最优子结构:长度为n的钢条的最优切割方案,包含子段的最优切割方案
  2. 重叠子问题:不同长度的切割方案会共享相同的子问题
  3. 一维状态:状态只依赖长度这一个维度,比矩阵连乘更简单

动态规划算法设计

1. 状态定义

r[i] = 长度为 i 的钢条的最大收益

2. 状态转移方程

价格数组定义p[j] 表示长度为 j 的钢条段的价格(直接出售,不切割)

根据价格表:

  • p[1] = 1(长度为1英寸的钢条价格1元)
  • p[2] = 4(长度为2英寸的钢条价格4元)
  • p[3] = 4(长度为3英寸的钢条价格4元)
  • p[4] = 7(长度为4英寸的钢条价格7元)
  • p[5] = 8(长度为5英寸的钢条价格8元)
  • p[6] = 9(长度为6英寸的钢条价格9元)

状态转移方程

r[i] = max{p[j] + r[i-j] | 1 ≤ j ≤ i}

逻辑解释

  • 对于长度为i的钢条,枚举第一段的长度j(1到i)
  • 如果第一段长度为j,收益为:
    • p[j]:第一段(长度为j)直接出售的价格
    • r[i-j]:剩余部分(长度为i-j)的最优切割收益
    • 总收益 = p[j] + r[i-j]
  • 选择使总收益最大的j
3. 填表过程(n=6)

初始化:r[0] = 0(长度为0的钢条收益为0)

i=1(长度为1英寸的钢条):

  • j=1:第一段长度为1,p[1] + r[0] = 1 + 0 = 1 ✓(唯一选择)
  • r[1] = 1,切割方案:不切割,整根出售(1英寸)

i=2(长度为2英寸的钢条):

  • j=1:第一段长度为1,p[1] + r[1] = 1 + 1 = 2
    • 方案:切成1+1,收益 = 1 + 1 = 2
  • j=2:第一段长度为2,p[2] + r[0] = 4 + 0 = 4 ✓(最大)
    • 方案:不切割,整根出售(2英寸),收益 = 4
  • r[2] = max{2, 4} = 4,最优方案:不切割,整根出售

i=3(长度为3英寸的钢条):

  • j=1:第一段长度为1,p[1] + r[2] = 1 + 4 = 5 ✓(最大)
    • 方案:切成1+2,收益 = 1 + 4 = 5
  • j=2:第一段长度为2,p[2] + r[1] = 4 + 1 = 5 ✓(同样最大)
    • 方案:切成2+1,收益 = 4 + 1 = 5
  • j=3:第一段长度为3,p[3] + r[0] = 4 + 0 = 4
    • 方案:不切割,整根出售(3英寸),收益 = 4
  • r[3] = max{5, 5, 4} = 5,最优方案:切成1+2或2+1

i=4(长度为4英寸的钢条):

  • j=1:第一段长度为1,p[1] + r[3] = 1 + 5 = 6
    • 方案:切成1+3,收益 = 1 + 5 = 6
  • j=2:第一段长度为2,p[2] + r[2] = 4 + 4 = 8 ✓(最大)
    • 方案:切成2+2,收益 = 4 + 4 = 8
  • j=3:第一段长度为3,p[3] + r[1] = 4 + 1 = 5
    • 方案:切成3+1,收益 = 4 + 1 = 5
  • j=4:第一段长度为4,p[4] + r[0] = 7 + 0 = 7
    • 方案:不切割,整根出售(4英寸),收益 = 7
  • r[4] = max{6, 8, 5, 7} = 8,最优方案:切成2+2

i=5(长度为5英寸的钢条):

  • j=1:第一段长度为1,p[1] + r[4] = 1 + 8 = 9 ✓(最大)
    • 方案:切成1+4,收益 = 1 + 8 = 9
  • j=2:第一段长度为2,p[2] + r[3] = 4 + 5 = 9 ✓(同样最大)
    • 方案:切成2+3,收益 = 4 + 5 = 9
  • j=3:第一段长度为3,p[3] + r[2] = 4 + 4 = 8
    • 方案:切成3+2,收益 = 4 + 4 = 8
  • j=4:第一段长度为4,p[4] + r[1] = 7 + 1 = 8
    • 方案:切成4+1,收益 = 7 + 1 = 8
  • j=5:第一段长度为5,p[5] + r[0] = 8 + 0 = 8
    • 方案:不切割,整根出售(5英寸),收益 = 8
  • r[5] = max{9, 9, 8, 8, 8} = 9,最优方案:切成1+4或2+3

i=6(长度为6英寸的钢条):

  • j=1:第一段长度为1,p[1] + r[5] = 1 + 9 = 10
    • 方案:切成1+5,收益 = 1 + 9 = 10
  • j=2:第一段长度为2,p[2] + r[4] = 4 + 8 = 12 ✓(最大)
    • 方案:切成2+4,进一步2+4可以优化为2+2+2,收益 = 4 + 8 = 12
  • j=3:第一段长度为3,p[3] + r[3] = 4 + 5 = 9
    • 方案:切成3+3,收益 = 4 + 5 = 9
  • j=4:第一段长度为4,p[4] + r[2] = 7 + 4 = 11
    • 方案:切成4+2,收益 = 7 + 4 = 11
  • j=5:第一段长度为5,p[5] + r[1] = 8 + 1 = 9
    • 方案:切成5+1,收益 = 8 + 1 = 9
  • j=6:第一段长度为6,p[6] + r[0] = 9 + 0 = 9
    • 方案:不切割,整根出售(6英寸),收益 = 9
  • r[6] = max{10, 12, 9, 11, 9, 9} = 12,最优方案:切成2+2+2(三个2英寸段)
4. 结果

最大收益:r[6] = 12元

最优切割方案:切成3段,每段2英寸(2 + 2 + 2 = 6)

5. 复杂度分析
  • 时间复杂度:O(n²)
  • 空间复杂度:O(n)

与矩阵连乘的对比

特性矩阵连乘钢条切割
状态维度二维 OPT(i,j)一维 r[i]
状态转移枚举切割点k枚举第一段长度j
复杂度O(n³)O(n²)
难度较复杂较简单

四、问题三:苹果买卖问题(贪心算法案例)

问题描述

一辆卡车从城市A到城市B,途经n个苹果市场。在每个市场i,可以以价格B[i]买入苹果,或以价格S[i]卖出苹果。

目标:在某个市场i买入,在后续市场j(j ≥ i)卖出,使利润 M = S[j] - B[i] 最大化。

约束

  • 卡车只能前进,不能后退
  • 只能进行一次买入和一次卖出
  • 可以在同一市场买入和卖出

为什么用贪心算法?

问题特性分析

  1. 最优子结构:如果(i, j)是最优买卖对,那么i一定是1到j之间价格最低的市场
  2. 贪心选择特性:对于每个卖出位置j,最优买入位置是j之前(包括j)价格最低的市场
  3. 局部最优保证全局最优:维护最低买入价格,计算每个位置的最大利润

与动态规划的区别

  • 不需要枚举所有可能的买卖对
  • 只需要一次遍历,维护最低买入价格
  • 复杂度从O(n²)降为O(n)

贪心算法设计

1. 核心思路

对于每个卖出位置j,最优买入位置是j之前(包括j)价格最低的市场。

2. 算法伪代码

输入

  • B[1..n]:每个市场的买入价格数组
  • S[1..n]:每个市场的卖出价格数组
  • n:市场数量

输出

  • optimalBuyMarket:最优买入市场编号
  • sellMarket:最优卖出市场编号
  • maxProfit:最大利润

变量说明

  • minBuy:到当前位置为止的最低买入价格
  • buyMarket:当前最低买入价格对应的市场编号
  • maxProfit:到目前为止的最大利润
  • sellMarket:当前最大利润对应的卖出市场编号
  • optimalBuyMarket:当前最大利润对应的买入市场编号
APPLE-TRADING(B, S, n)
1. minBuy ← B[1]          // 初始化:第一个市场的最低买入价格就是B[1]2. buyMarket ← 1          // 初始化:第一个市场作为当前最低买入价格的市场3. maxProfit ← S[1] - B[1]  // 初始化:第一个市场的利润(可能为负,表示亏损)4. sellMarket ← 1         // 初始化:第一个市场作为当前最优卖出市场5. // 从第二个市场开始遍历6. for j ← 2 to n// 遍历每个市场j,作为潜在的卖出市场7.     do if B[j] < minBuy// 如果市场j的买入价格低于当前最低买入价格8.           then minBuy ← B[j]// 更新最低买入价格为B[j]9.                buyMarket ← j// 更新最低买入价格对应的市场为j// 注意:这里更新buyMarket,但optimalBuyMarket可能不变// 因为虽然找到了更低的买入价,但可能利润不是最大的10.    // 计算在市场j卖出的利润// 使用当前已知的最低买入价格(可能在市场j之前)11.    profit ← S[j] - minBuy// 利润 = 市场j的卖出价格 - 到j为止的最低买入价格// 这保证了买入市场 ≤ 卖出市场j(满足约束条件)12.    if profit > maxProfit// 如果当前利润大于已知的最大利润13.         then maxProfit ← profit// 更新最大利润14.              sellMarket ← j// 更新最优卖出市场为j15.              optimalBuyMarket ← buyMarket// 更新最优买入市场为当前buyMarket// 注意:这里buyMarket是到j为止的最低买入价格对应的市场// 这保证了(optimalBuyMarket, sellMarket)是最优买卖对16. 
17. return (optimalBuyMarket, sellMarket, maxProfit)// 返回最优买入市场、最优卖出市场和最大利润

算法核心逻辑

  1. 维护最低买入价格(第7-9行):在遍历过程中,始终记录到当前位置为止的最低买入价格及其对应的市场。这是贪心选择的关键——对于每个卖出位置j,最优买入位置一定是j之前(包括j)价格最低的市场。

  2. 计算当前利润(第11行):对于每个市场j,计算"在最低买入价格的市场买入,在市场j卖出"的利润。这利用了贪心选择特性,不需要枚举所有可能的买入位置。

  3. 更新最优解(第12-15行):如果当前利润大于已知的最大利润,更新最大利润和对应的买卖市场对。

为什么这样设计是正确的?

  • 对于任意卖出位置j,如果我们已经知道j之前(包括j)的最低买入价格是minBuy,对应的市场是buyMarket,那么(buyMarket, j)就是在j卖出的最优买卖对。
  • 因为如果存在更优的买入位置k(k < j且B[k] < minBuy),那么算法应该已经在遍历到k时更新了minBuy和buyMarket。
  • 因此,我们只需要遍历一次,对每个卖出位置j,用当前已知的最低买入价格计算利润即可。
3. 算法执行示例

假设输入:

  • B = [5, 3, 2, 4, 1, 6] // 买入价格
  • S = [4, 6, 5, 7, 3, 8] // 卖出价格

执行过程详解

列说明

  • j:当前遍历到的市场编号(作为潜在的卖出市场)
  • B[j]:市场j的买入价格
  • S[j]:市场j的卖出价格
  • minBuy:到市场j为止的最低买入价格
  • buyMarket:当前最低买入价格对应的市场编号
  • profit:在市场j卖出的利润(= S[j] - minBuy)
  • maxProfit:到目前为止的最大利润
  • sellMarket:当前最大利润对应的卖出市场
  • optimalBuyMarket:当前最大利润对应的买入市场

逐步执行过程

j=1(初始化,处理第一个市场)

  • B[1] = 5S[1] = 4
  • minBuy = 5(第一个市场,最低买入价格就是B[1])
  • buyMarket = 1(最低买入价格对应的市场)
  • profit = S[1] - minBuy = 4 - 5 = -1(亏损1元)
  • maxProfit = -1(当前最大利润,虽然是亏损)
  • sellMarket = 1optimalBuyMarket = 1
  • 含义:如果只在市场1买卖,会亏损1元

j=2(处理第二个市场)

  • B[2] = 3S[2] = 6
  • 更新最低买入价格:因为 B[2] = 3 < minBuy = 5,所以:
    • minBuy = 3(更新为更低的价格)
    • buyMarket = 2(更新为市场2)
  • profit = S[2] - minBuy = 6 - 3 = 3(利润3元)
  • 更新最大利润:因为 profit = 3 > maxProfit = -1,所以:
    • maxProfit = 3
    • sellMarket = 2(最优卖出市场更新为市场2)
    • optimalBuyMarket = 2(最优买入市场更新为市场2)
  • 含义:在市场2买入(价格3),在市场2卖出(价格6),利润3元。这是目前最优方案。

j=3(处理第三个市场)

  • B[3] = 2S[3] = 5
  • 更新最低买入价格:因为 B[3] = 2 < minBuy = 3,所以:
    • minBuy = 2(更新为更低的价格)
    • buyMarket = 3(更新为市场3)
  • profit = S[3] - minBuy = 5 - 2 = 3(利润3元)
  • 不更新最大利润:因为 profit = 3 = maxProfit = 3,没有更优
  • sellMarket = 2optimalBuyMarket = 2(保持不变)
  • 含义:虽然找到了更低的买入价格(市场3,价格2),但在市场3卖出的利润(3元)没有超过当前最优利润(3元),所以最优方案仍然是(市场2,市场2)。

j=4(处理第四个市场)

  • B[4] = 4S[4] = 7
  • 不更新最低买入价格:因为 B[4] = 4 > minBuy = 2,所以 minBuybuyMarket 保持不变
  • profit = S[4] - minBuy = 7 - 2 = 5(利润5元)
    • 注意:这里使用的是市场3的买入价格(minBuy=2),而不是市场4的买入价格
  • 更新最大利润:因为 profit = 5 > maxProfit = 3,所以:
    • maxProfit = 5
    • sellMarket = 4(最优卖出市场更新为市场4)
    • optimalBuyMarket = 3(最优买入市场更新为市场3,即当前的buyMarket)
  • 含义:在市场3买入(价格2),在市场4卖出(价格7),利润5元。这是新的最优方案。

j=5(处理第五个市场)

  • B[5] = 1S[5] = 3
  • 更新最低买入价格:因为 B[5] = 1 < minBuy = 2,所以:
    • minBuy = 1(更新为更低的价格)
    • buyMarket = 5(更新为市场5)
  • profit = S[5] - minBuy = 3 - 1 = 2(利润2元)
  • 不更新最大利润:因为 profit = 2 < maxProfit = 5,所以最优方案保持不变
  • sellMarket = 4optimalBuyMarket = 3(保持不变)
  • 含义:虽然找到了更低的买入价格(市场5,价格1),但在市场5卖出的利润(2元)没有超过当前最优利润(5元),所以最优方案仍然是(市场3,市场4)。

j=6(处理第六个市场,最终结果)

  • B[6] = 6S[6] = 8
  • 不更新最低买入价格:因为 B[6] = 6 > minBuy = 1,所以 minBuybuyMarket 保持不变
  • profit = S[6] - minBuy = 8 - 1 = 7(利润7元)
    • 注意:这里使用的是市场5的买入价格(minBuy=1),而不是市场6的买入价格
  • 更新最大利润:因为 profit = 7 > maxProfit = 5,所以:
    • maxProfit = 7
    • sellMarket = 6(最优卖出市场更新为市场6)
    • optimalBuyMarket = 5(最优买入市场更新为市场5,即当前的buyMarket)
  • 含义:在市场5买入(价格1),在市场6卖出(价格8),利润7元。这是最终的最优方案。

最终结果

  • 最优买入市场:5(价格1元/斤)
  • 最优卖出市场:6(价格8元/斤)
  • 最大利润:7元/斤

关键洞察

  1. minBuy和buyMarket的更新:每当遇到更低的买入价格时更新,这保证了对于每个卖出位置j,我们总是使用j之前(包括j)的最低买入价格。
  2. profit的计算:总是使用当前的minBuy计算,而不是B[j],这体现了贪心选择特性。
  3. 最优解的更新:只有当profit大于maxProfit时才更新,这保证了我们找到的是全局最优解。
4. 正确性证明

贪心选择特性:对于任意卖出位置j,最优买入位置一定是j之前(包括j)价格最低的市场。

证明(反证法):

  • 假设存在更优的买入位置k(k < i且B[k] < B[i]),其中i是j之前价格最低的市场
  • 那么(k, j)的利润 = S[j] - B[k] > S[j] - B[i] = (i, j)的利润
  • 但这与"i是j之前价格最低的市场"矛盾,因为如果B[k] < B[i],算法应该已经将minBuy更新为B[k]
5. 复杂度分析
  • 时间复杂度:O(n)(单次遍历)
  • 空间复杂度:O(1)(只使用常数个变量)

与动态规划的对比

如果用动态规划:

  • 状态定义:F[i][j] = 在市场i买入、市场j卖出的利润
  • 需要计算所有O(n²)个状态
  • 复杂度:O(n²)

贪心算法的优势:

  • 只需要O(n)时间
  • 代码更简单
  • 空间复杂度O(1)

五、三种问题的算法选择决策树

问题是否满足最优子结构?
├─ 否 → 不适合用DP或贪心(考虑其他算法)
└─ 是 → 是否满足贪心选择特性?├─ 是 → 优先用贪心算法│   ├─ 苹果买卖:O(n)时间,O(1)空间│   └─ 区间调度(无权重):O(n log n)时间└─ 否 → 用动态规划├─ 矩阵连乘:O(n³)时间,O(n²)空间└─ 钢条切割:O(n²)时间,O(n)空间

问题特征对比表

问题算法类型状态维度时间复杂度空间复杂度关键特性
矩阵连乘动态规划二维O(n³)O(n²)重叠子问题、最优子结构
钢条切割动态规划一维O(n²)O(n)重叠子问题、最优子结构
苹果买卖贪心算法无状态表O(n)O(1)贪心选择特性、最优子结构

六、总结:算法选择的决策框架

核心原则

  1. 先判断最优子结构:如果不满足,不适合用DP或贪心
  2. 再判断贪心选择特性:如果满足,优先用贪心(通常更简单高效)
  3. 最后选择动态规划:如果只满足最优子结构,用DP

性能对比

算法类型时间复杂度空间复杂度实现难度适用场景
贪心算法O(n) 或 O(n log n)O(1) 或 O(n)简单满足贪心选择特性
动态规划O(n²) 或 O(n³)O(n) 或 O(n²)中等满足最优子结构
http://www.dtcms.com/a/614226.html

相关文章:

  • 智能化时代的SEO关键词优化新策略与实践探索
  • 免费外贸建站平台访问网页的流程
  • 宁夏水利厅建设处网站阳信网站建设
  • 传导案例:某3KW 开关电源整改案例分享
  • 针对特定业务场景(如金融交易、日志处理)选择最优的MPSC实现
  • 练习python题目小记(五)
  • 怎么建立网站 个人云速网站建设公司
  • 怎么用自己电脑做网站社区教育网站建设方案
  • 卫星互联网:弥合数字鸿沟的“天基网络“
  • 选择排序的原理及示例
  • 【开题答辩全过程】以 房产网站为例,包含答辩的问题和答案
  • spring Profile
  • 当AI不再等待指令:智能体工作流如何重构商业逻辑
  • 手机网站建设哪儿好wordpress怎么固定导航栏
  • 基于大数据Python豆瓣电影可视化系统 电影数据爬虫 数据清洗+数据可视化 Flask+requests (MySQL+Echarts 源码+文档)✅
  • 自己建设网站怎么挣钱微信小程序推广软件
  • 专门做继电器的网站泉州做网站优化哪家好
  • 信息安全工程师软考精通:第六章物理与环境安全深度解析
  • Spring Data 什么是Spring Data 理解
  • 石家庄做网站需要多少钱黄骅港旅游景点大全
  • 破解入门学习笔记题四十六
  • go-context创建及使用详细概括
  • go进阶学习
  • 做网站建设培训wordpress如何添加网站地图
  • 网站关键词搜索排名优化郑州建设网站定制
  • Java输入输出:编程世界的入口和出口
  • Xcode编译C语言:提升编译效率与调试技巧
  • MONGO-EXPRESS Docker 容器化部署指南
  • 免费psd图片素材网站邯郸网站开发
  • IDEA配置Maven