从青铜到王者:LeetCode-Go动态规划完全攻略(含斐波那契/股票问题详解)

从青铜到王者:LeetCode-Go动态规划完全攻略(含斐波那契/股票问题详解)

【免费下载链接】LeetCode-Go 该内容是使用Go语言编写的LeetCode题目的完整解决方案集合,实现了100%的测试覆盖率,并且运行时间优于所有题目100%的提交结果。 【免费下载链接】LeetCode-Go 项目地址: https://gitcode.com/GitHub_Trending/le/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题目的分析,我们总结出动态规划解题的通用步骤:

四步解题法

  1. 定义状态:明确dp[i]或dp[i][j]代表什么
  2. 确定转移方程:dp[i]如何从dp[i-1]等前序状态推导而来
  3. 初始化边界条件:设置递归或迭代的起点
  4. 计算顺序与空间优化:确定遍历方向,考虑滚动数组等优化

常见问题类型与模板

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+道从易到难的完整题解。每个解法都标注了时间/空间复杂度,并提供多种实现方式对比。

如何高效使用本项目

  1. 按标签刷题:从leetcode/topic/dynamic-programming目录开始
  2. 对比学习:重点关注同一问题的不同DP实现(如斐波那契的五种解法)
  3. 实战演练:尝试修改状态定义或转移方程,观察性能变化

进阶资源

  • 官方题解:README.md提供的题目分类与难度标注
  • 视频讲解:项目配套的B站动态规划专题(需配合源码食用)
  • 面试模拟:ctl/main.go提供的随机刷题功能

总结与展望

动态规划作为算法面试的"拦路虎",其实是有章可循的。通过本文介绍的方法论和LeetCode-Go项目中的实战案例,你已经掌握了从基础到进阶的全部要点。记住,动态规划的核心不是背诵模板,而是培养将复杂问题分解为子问题的思维能力

建议接下来从「打家劫舍」「最长公共子序列」等经典题目开始练习,逐步建立自己的DP解题框架。当你能独立解决「买卖股票的最佳时机IV」这类Hard题目时,恭喜你已经达到动态规划的精通水平!

如果你觉得本文对你有帮助,欢迎点赞收藏,并关注LeetCode-Go项目获取更多算法干货。下期我们将带来「图论算法专题」,敬请期待!

【免费下载链接】LeetCode-Go 该内容是使用Go语言编写的LeetCode题目的完整解决方案集合,实现了100%的测试覆盖率,并且运行时间优于所有题目100%的提交结果。 【免费下载链接】LeetCode-Go 项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Go

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值