元素码农
基础
UML建模
数据结构
算法
设计模式
网络
TCP/IP协议
HTTPS安全机制
WebSocket实时通信
数据库
sqlite
postgresql
clickhouse
后端
rust
go
java
php
mysql
redis
mongodb
etcd
nats
zincsearch
前端
浏览器
javascript
typescript
vue3
react
游戏
unity
unreal
C++
C#
Lua
App
android
ios
flutter
react-native
安全
Web安全
测试
软件测试
自动化测试 - Playwright
人工智能
Python
langChain
langGraph
运维
linux
docker
工具
git
svn
🌞
🌙
目录
▶
算法基础
▶
复杂度分析
时间复杂度
空间复杂度
渐进符号
▶
算法思想
分治法
贪心算法
回溯法
概率算法
枚举算法
递推算法
递归算法
动态规划
分支限界法
模拟算法
▶
算法分析
正确性证明
性能优化
算法比较
▶
搜索算法
▶
基本搜索
顺序搜索
二分搜索
插值搜索
▶
图搜索
深度优先搜索
广度优先搜索
启发式搜索
▶
高级搜索
双向搜索
A*算法
模式匹配
▶
排序算法
▶
比较类排序
冒泡排序
快速排序
归并排序
插入排序
选择排序
▶
非比较类排序
计数排序
基数排序
桶排序
▶
高级排序
堆排序
希尔排序
外部排序
拓扑排序
▶
图论算法
▶
图的表示
邻接矩阵
邻接表
边集数组
▶
最短路径
Dijkstra算法
Floyd算法
Bellman-Ford算法
最短路径概述
▶
生成树
Prim算法
Kruskal算法
并查集
最小生成树
▶
图的连通性
强连通分量
割点与桥
双连通分量
欧拉路径
▶
动态规划
▶
基础概念
最优子结构
重叠子问题
状态转移方程
▶
经典问题
背包问题
最长公共子序列
编辑距离
▶
优化技巧
空间优化
状态压缩
区间动态规划
▶
字符串算法
▶
字符串匹配
KMP算法
Boyer-Moore算法
Sunday算法
▶
字符串处理
字典树
后缀数组
字符串哈希
▶
压缩算法
游程编码
Huffman编码
LZ77算法
发布时间:
2025-03-21 19:47
↑
☰
# 最优子结构 最优子结构是动态规划的核心特征之一,它是指一个问题的最优解包含其子问题的最优解。理解和识别最优子结构对于设计动态规划算法至关重要。 ## 基本概念 ### 定义 最优子结构具有以下特点: - 问题的最优解由子问题的最优解构成 - 子问题可以独立求解,不受其他子问题的影响 - 子问题的解可以被重复使用 ### 示例说明 以最短路径问题为例: - 如果路径P是从顶点A到顶点B的最短路径 - 路径P经过顶点C - 则路径P中从A到C的部分必定是A到C的最短路径 - 路径P中从C到B的部分必定是C到B的最短路径 ## 识别最优子结构 ### 关键步骤 1. 定义子问题 - 明确原问题和子问题的关系 - 确保子问题的规模更小 2. 验证最优性 - 证明子问题的最优解能构成原问题的最优解 - 确认子问题之间是独立的 3. 建立递推关系 - 找出原问题与子问题之间的数学关系 - 写出状态转移方程 ### 常见特征 1. 路径特征 - 最短路径 - 最长路径 - 最小成本路径 2. 序列特征 - 子序列问题 - 子串问题 - 匹配问题 ## 经典问题分析 ### 最长递增子序列 ```go // 最长递增子序列问题展示了典型的最优子结构 func lengthOfLIS(nums []int) int { if len(nums) == 0 { return 0 } // dp[i]表示以nums[i]结尾的最长递增子序列长度 dp := make([]int, len(nums)) for i := range dp { dp[i] = 1 // 初始化为1,因为每个数字本身就是长度为1的递增子序列 } maxLen := 1 // 对每个位置i,考虑前面所有小于nums[i]的数字 for i := 1; i < len(nums); i++ { for j := 0; j < i; j++ { if nums[i] > nums[j] { dp[i] = max(dp[i], dp[j]+1) } } maxLen = max(maxLen, dp[i]) } return maxLen } func max(a, b int) int { if a > b { return a } return b } ``` ### 矩阵链乘法 ```go // 矩阵链乘法问题展示了如何利用最优子结构求解最优括号化方案 func matrixChainOrder(dimensions []int) int { n := len(dimensions) - 1 // dp[i][j]表示从第i个矩阵到第j个矩阵的最小乘法次数 dp := make([][]int, n) for i := range dp { dp[i] = make([]int, n) } // l是当前考虑的链长度 for l := 2; l <= n; l++ { // i是子链的起点 for i := 0; i < n-l+1; i++ { j := i + l - 1 // j是子链的终点 dp[i][j] = math.MaxInt32 // 尝试在不同位置k处分割 for k := i; k < j; k++ { cost := dp[i][k] + dp[k+1][j] + dimensions[i]*dimensions[k+1]*dimensions[j+1] dp[i][j] = min(dp[i][j], cost) } } } return dp[0][n-1] } func min(a, b int) int { if a < b { return a } return b } ``` ## 应用技巧 ### 构建解决方案 1. 问题分析 - 确定问题是否具有最优子结构 - 找出子问题与原问题的关系 2. 设计算法 - 选择合适的状态表示 - 设计状态转移方程 - 确定边界条件 3. 优化实现 - 考虑空间优化 - 利用记忆化搜索 - 选择合适的数据结构 ### 常见陷阱 1. 子问题依赖 - 确保子问题之间真正独立 - 避免重复计算相同状态 2. 状态设计 - 状态表示要完整 - 避免状态冗余 3. 边界处理 - 正确初始化边界条件 - 处理特殊输入情况 ## 总结 1. 最优子结构的重要性 - 是动态规划的基础 - 帮助简化复杂问题 - 提供解决问题的思路 2. 应用要点 - 准确识别最优子结构 - 正确定义子问题 - 设计高效的算法 3. 实践建议 - 从简单问题开始练习 - 多总结问题的共性 - 注意优化算法效率