从青铜到王者:LeetCode-Go动态规划完全攻略(含斐波那契/股票问题详解)
你是否还在为动态规划(Dynamic Programming, DP)问题感到头疼?面对"子序列""最优解"等关键词时无从下手?本文将通过LeetCode-Go项目中的实战案例,从基础的斐波那契数列到复杂的股票交易问题,带你掌握动态规划的核心思维与解题套路,让你在面试中轻松应对90%的DP题目。
动态规划入门:斐波那契数列的五种解法
动态规划的本质是将复杂问题分解为重叠子问题,通过存储中间结果避免重复计算。以经典的斐波那契数列为例,LeetCode-Go项目提供了从暴力递归到矩阵快速幂的完整演进方案,完美展示了动态规划的优化过程。
从递归到记忆化搜索
最直观的递归解法存在严重的重复计算问题,时间复杂度高达O(2ⁿ):
// 解法一 递归法 时间复杂度 O(2^n),空间复杂度 O(n)
func fib(N int) int {
if N <= 1 {
return N
}
return fib(N-1) + fib(N-2)
}
通过引入缓存(记忆化搜索),将时间复杂度优化至O(n):
// 解法二 自底向上的记忆化搜索 时间复杂度 O(n),空间复杂度 O(n)
func fib1(N int) int {
if N <= 1 {
return N
}
cache := map[int]int{0: 0, 1: 1}
for i := 2; i <= N; i++ {
cache[i] = cache[i-1] + cache[i-2]
}
return cache[N]
}
完整代码实现:0509.Fibonacci-Number/509. Fibonacci Number.go
空间优化:滚动数组技巧
进一步观察发现,计算第n项只需要前两项的值,因此可以用三个变量替代完整缓存,将空间复杂度降至O(1):
// 解法四 优化版的 dp,节约内存空间 时间复杂度 O(n),空间复杂度 O(1)
func fib3(N int) int {
if N <= 1 {
return N
}
if N == 2 {
return 1
}
current, prev1, prev2 := 0, 1, 1
for i := 3; i <= N; i++ {
current = prev1 + prev2
prev2 = prev1
prev1 = current
}
return current
}
这种滚动数组技巧在各类DP问题中都有广泛应用,例如最长公共子序列、编辑距离等问题均可采用类似优化。
进阶:矩阵快速幂与公式法
对于超大n值(如n=1e6),项目中还提供了时间复杂度O(log n)的矩阵快速幂解法:
// 解法五 矩阵快速幂 时间复杂度 O(log n)
// | 1 1 | ^ n = | F(n+1) F(n) |
// | 1 0 | | F(n) F(n-1) |
func fib4(N int) int {
if N <= 1 {
return N
}
var A = [2][2]int{{1, 1}, {1, 0}}
A = matrixPower(A, N-1)
return A[0][0]
}
而利用黄金分割率的数学公式法则能达到O(1)时间复杂度:
func fib5(N int) int {
var goldenRatio float64 = float64((1 + math.Sqrt(5)) / 2)
return int(math.Round(math.Pow(goldenRatio, float64(N)) / math.Sqrt(5)))
}
实战进阶:股票交易问题的动态规划模型
掌握基础后,我们来看动态规划在复杂场景中的应用。股票交易问题是LeetCode高频考点,LeetCode-Go项目通过状态机思想建立的DP模型,可通解所有股票系列问题。
状态定义与转移方程
股票问题的核心是定义清晰的状态变量。以"最佳买卖股票时机含冷冻期"为例,我们需要跟踪三个状态:
dp[i][0]:持有股票的最大收益dp[i][1]:不持有股票且在冷冻期的最大收益dp[i][2]:不持有股票且不在冷冻期的最大收益
状态转移方程如下:
dp[i][0] = max(dp[i-1][0], dp[i-1][2] - prices[i])
dp[i][1] = dp[i-1][0] + prices[i]
dp[i][2] = max(dp[i-1][2], dp[i-1][1])
代码实现与空间优化
基于上述模型,LeetCode-Go实现了空间优化版的解决方案(完整代码):
func maxProfit(prices []int) int {
if len(prices) == 0 {
return 0
}
n := len(prices)
dp0, dp1, dp2 := -prices[0], 0, 0
for i := 1; i < n; i++ {
newDp0 := max(dp0, dp2 - prices[i])
newDp1 := dp0 + prices[i]
newDp2 := max(dp2, dp1)
dp0, dp1, dp2 = newDp0, newDp1, newDp2
}
return max(dp1, dp2)
}
通过将二维DP数组压缩为三个变量,空间复杂度从O(n)降至O(1),这也是LeetCode-Go项目中常见的优化技巧。
动态规划解题方法论
通过对LeetCode-Go项目中数十道DP题目的分析,我们总结出动态规划解题的通用步骤:
四步解题法
- 定义状态:明确dp[i]或dp[i][j]代表什么
- 确定转移方程:dp[i]如何从dp[i-1]等前序状态推导而来
- 初始化边界条件:设置递归或迭代的起点
- 计算顺序与空间优化:确定遍历方向,考虑滚动数组等优化
常见问题类型与模板
LeetCode-Go项目将动态规划问题分为六大类,每类都有固定的解题模板:
| 问题类型 | 典型题目 | 状态定义 | 转移方程 |
|---|---|---|---|
| 线性DP | 最长递增子序列 | dp[i] = 以i结尾的LIS长度 | dp[i] = max(dp[j]+1) for j < i |
| 区间DP | 最长回文子串 | dp[i][j] = i~j是否为回文 | dp[i][j] = s[i]==s[j] && dp[i+1][j-1] |
| 背包问题 | 零钱兑换 | dp[i] = 凑i元的最少硬币数 | dp[i] = min(dp[i-coins[j]]+1) |
| 状态机DP | 股票交易 | dp[i][k][0/1] = 第i天k次交易后持仓状态 | 见股票问题模型 |
| 计数DP | 不同路径 | dp[i][j] = 到(i,j)的路径数 | dp[i][j] = dp[i-1][j] + dp[i][j-1] |
| 树形DP | 二叉树中的最大路径和 | 后序遍历计算节点贡献 | max(left, right) + node.Val |
LeetCode-Go中的DP实战案例
1. 最长重复子数组(中等)
718. Maximum Length of Repeated Subarray使用二维DP解决:
func findLength(A []int, B []int) int {
m, n := len(A), len(B)
dp := make([][]int, m+1)
for i := range dp {
dp[i] = make([]int, n+1)
}
maxLen := 0
for i := m-1; i >= 0; i-- {
for j := n-1; j >= 0; j-- {
if A[i] == B[j] {
dp[i][j] = dp[i+1][j+1] + 1
if dp[i][j] > maxLen {
maxLen = dp[i][j]
}
} else {
dp[i][j] = 0
}
}
}
return maxLen
}
2. 超级鸡蛋掉落(困难)
887. Super Egg Drop展示了非常规的状态定义技巧:
// dp[k][m] = k个鸡蛋扔m次能确定的最大楼层数
func superEggDrop(K int, N int) int {
dp := make([][]int, K+1)
for i := range dp {
dp[i] = make([]int, N+1)
}
m := 0
for dp[K][m] < N {
m++
for k := 1; k <= K; k++ {
dp[k][m] = dp[k][m-1] + dp[k-1][m-1] + 1
}
}
return m
}
项目实战与资源推荐
LeetCode-Go项目将所有动态规划题目归类在Dynamic Programming标签下,包含100+道从易到难的完整题解。每个解法都标注了时间/空间复杂度,并提供多种实现方式对比。
如何高效使用本项目
- 按标签刷题:从leetcode/topic/dynamic-programming目录开始
- 对比学习:重点关注同一问题的不同DP实现(如斐波那契的五种解法)
- 实战演练:尝试修改状态定义或转移方程,观察性能变化
进阶资源
- 官方题解:README.md提供的题目分类与难度标注
- 视频讲解:项目配套的B站动态规划专题(需配合源码食用)
- 面试模拟:ctl/main.go提供的随机刷题功能
总结与展望
动态规划作为算法面试的"拦路虎",其实是有章可循的。通过本文介绍的方法论和LeetCode-Go项目中的实战案例,你已经掌握了从基础到进阶的全部要点。记住,动态规划的核心不是背诵模板,而是培养将复杂问题分解为子问题的思维能力。
建议接下来从「打家劫舍」「最长公共子序列」等经典题目开始练习,逐步建立自己的DP解题框架。当你能独立解决「买卖股票的最佳时机IV」这类Hard题目时,恭喜你已经达到动态规划的精通水平!
如果你觉得本文对你有帮助,欢迎点赞收藏,并关注LeetCode-Go项目获取更多算法干货。下期我们将带来「图论算法专题」,敬请期待!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



