动态规划入门到精通:GitHub_Trending/le/LeetCode-Book实战教程

动态规划入门到精通:GitHub_Trending/le/LeetCode-Book实战教程

【免费下载链接】LeetCode-Book 《剑指 Offer》 Python, Java, C++ 解题代码,LeetBook《图解算法数据结构》配套代码仓 【免费下载链接】LeetCode-Book 项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Book

你还在为动态规划题抓狂吗?

刷题时遇到动态规划(Dynamic Programming, DP)题就卡壳?看题解时觉得"我上我也行",自己做时却无从下手?本文将通过LeetCode-Book项目中的10+实战例题,从理论到代码实现,带你彻底掌握动态规划的解题方法论。

读完本文你将获得:

  • 动态规划本质及与分治算法的核心差异
  • 四步解题框架(状态定义→转移方程→初始状态→返回值)
  • 6类经典题型全解析(含空间优化技巧)
  • Python/Java/C++多语言实现代码
  • 从入门到面试的完整学习路径

一、动态规划核心思想

1.1 动态规划定义

动态规划是一种通过将复杂问题分解为重叠子问题,并存储子问题解来避免重复计算的算法思想。与分治算法的主要区别在于:

特性分治算法动态规划
子问题关系独立不重叠存在重叠子问题
优化目标一般问题的解最优解(最大值/最小值)
存储需求无需存储中间结果需要存储子问题解

1.2 两个核心特性

重叠子问题:不同问题包含相同子问题,如斐波那契数列中f(5)f(4)都需要计算f(3)

mermaid

最优子结构:问题的最优解包含子问题的最优解,如蛋糕切割问题中,最优切割方案必然包含子段的最优切割

1.3 四步解题框架

  1. 状态定义:定义dp[i]dp[i][j]代表的具体含义
  2. 转移方程:建立子问题之间的关系(核心难点)
  3. 初始状态:确定基础子问题的解
  4. 返回值:确定最终要返回的dp值

二、基础题型实战

2.1 一维DP:跳跃训练(斐波那契变种)

问题:青蛙一次可跳1或2级台阶,求跳n级台阶的方法数

解题思路

  • 状态定义:dp[i]表示跳i级台阶的方法数
  • 转移方程:dp[i] = dp[i-1] + dp[i-2](最后一步跳1级或2级)
  • 初始状态:dp[0]=1, dp[1]=1
  • 空间优化:用两个变量交替存储,空间复杂度从O(n)降至O(1)

代码实现

class Solution:
    def trainWays(self, num: int) -> int:
        a, b = 1, 1
        for _ in range(num):
            a, b = b, (a + b) % 1000000007
        return a
class Solution {
    public int trainWays(int num) {
        int a = 1, b = 1, sum;
        for(int i = 0; i < num; i++){
            sum = (a + b) % 1000000007;
            a = b;
            b = sum;
        }
        return a;
    }
}

2.2 最大子数组和( Kadane算法 )

问题:给定整数数组,求具有最大和的连续子数组

解题思路

  • 状态定义:dp[i]表示以第i个元素结尾的最大子数组和
  • 转移方程:dp[i] = max(nums[i], dp[i-1]+nums[i])
  • 初始状态:dp[0] = nums[0]
  • 空间优化:直接修改原数组,空间复杂度O(1)

代码实现

class Solution:
    def maxSales(self, sales: List[int]) -> int:
        for i in range(1, len(sales)):
            sales[i] += max(sales[i - 1], 0)
        return max(sales)

复杂度分析

  • 时间复杂度:O(n),只需遍历一次数组
  • 空间复杂度:O(1),原地修改数组

三、二维DP与空间优化

3.1 珠宝的最大价值(二维DP)

问题:m×n网格中每个单元格有价值,从左上角到右下角(只能右/下移动),求最大价值和

解题思路

  • 状态定义:dp[i][j]表示到达(i,j)的最大价值
  • 转移方程:dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + grid[i][j]
  • 空间优化:直接修改原网格,省去dp数组

代码实现

class Solution:
    def jewelleryValue(self, frame: List[List[int]]) -> int:
        m, n = len(frame), len(frame[0])
        for j in range(1, n):  # 初始化第一行
            frame[0][j] += frame[0][j - 1]
        for i in range(1, m):  # 初始化第一列
            frame[i][0] += frame[i - 1][0]
        for i in range(1, m):
            for j in range(1, n):
                frame[i][j] += max(frame[i][j - 1], frame[i - 1][j])
        return frame[-1][-1]

3.2 最长递增子序列(O(n²)到O(n log n)优化)

问题:求数组中最长严格递增子序列的长度

动态规划解法

  • 状态定义:dp[i]表示以nums[i]结尾的最长递增子序列长度
  • 转移方程:dp[i] = max(dp[j]+1 for j < i if nums[j] < nums[i])
  • 时间复杂度:O(n²),空间复杂度:O(n)

二分优化解法

  • 维护 tails 数组,tails[i]表示长度为i+1的子序列尾部元素
  • 二分查找当前元素可插入的位置,更新tails数组
  • 时间复杂度:O(n log n),空间复杂度:O(n)

代码实现

# O(n log n)解法
class Solution:
    def lengthOfLIS(self, nums: [int]) -> int:
        tails, res = [0] * len(nums), 0
        for num in nums:
            i, j = 0, res
            while i < j:
                m = (i + j) // 2
                if tails[m] < num: 
                    i = m + 1
                else: 
                    j = m
            tails[i] = num
            if j == res: 
                res += 1
        return res

四、经典问题实战

4.1 买卖股票的最佳时机(状态压缩)

问题:只能一次买入卖出,求最大利润

解题思路

  • 遍历过程中记录最低价格和最大利润
  • 状态压缩:用两个变量替代dp数组
class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        cost, profit = float('+inf'), 0
        for price in prices:
            cost = min(cost, price)
            profit = max(profit, price - cost)
        return profit

4.2 打家劫舍(相邻约束问题)

问题:不能抢劫相邻房屋,求最大抢劫金额

解题思路

  • 状态定义:dp[i]表示前i间房屋的最大抢劫金额
  • 转移方程:dp[i] = max(dp[i-1], dp[i-2]+nums[i])
  • 空间优化:用两个变量交替存储
class Solution:
    def rob(self, nums: List[int]) -> int:
        cur, pre = 0, 0
        for num in nums:
            cur, pre = max(pre + num, cur), cur
        return cur

4.3 数字翻译(字符串DP)

问题:0-25对应A-Z,求数字有多少种翻译方法

解题思路

  • 状态定义:dp[i]表示前i位数字的翻译方法数
  • 转移方程:dp[i] = dp[i-1] + (dp[i-2] if 10<=num<=25 else 0)
class Solution:
    def translateNum(self, num: int) -> int:
        s = str(num)
        a = b = 1
        for i in range(2, len(s) + 1):
            a, b = (a + b if "10" <= s[i-2:i] <= "25" else a), a
        return a

五、动态规划优化技巧总结

5.1 空间优化方法

方法适用场景空间复杂度优化
变量替代数组只依赖前1-2个状态O(n) → O(1)
原地修改二维DP且只依赖相邻状态O(mn) → O(1)
滚动数组依赖前k个状态O(nk) → O(k)

5.2 时间优化方法

方法适用场景时间复杂度优化
二分查找递增/递减子序列问题O(n²) → O(n log n)
前缀和区间和问题O(n²) → O(n)
状态压缩高维状态问题指数级 → 多项式级

六、学习路径与资源推荐

6.1 动态规划学习路径

mermaid

6.2 LeetCode-Book项目使用指南

  1. 项目获取

    git clone https://gitcode.com/GitHub_Trending/le/LeetCode-Book
    
  2. 重点目录

    • leetbook_ioa/docs/:图解算法数据结构配套文档
    • sword_for_offer/docs/:剑指Offer题解
    • selected_coding_interview/docs/:精选面试题
  3. 推荐刷题顺序

    1. 动态规划解题框架.md
    2. 斐波那契数列 → 爬楼梯 → 最大子数组
    3. 最长递增子序列 → 打家劫舍 → 买卖股票系列
    4. 区间DP → 状态压缩DP

七、总结与展望

动态规划作为算法面试的"拦路虎",掌握它需要理解其本质而非死记硬背。本文通过LeetCode-Book项目中的实战例题,系统讲解了从基础到进阶的动态规划知识,包括:

  • 核心思想:重叠子问题与最优子结构
  • 解题框架:状态定义→转移方程→初始状态→返回值
  • 优化技巧:空间压缩与时间优化方法
  • 实战经验:6类经典题型全解析

记住,动态规划的关键在于多练多总结,建议按照本文提供的学习路径,每天至少练习1道DP题,坚持1个月必能看到显著进步!

收藏本文,下次刷DP题时对照实战,效率提升300%!

下期预告:《贪心算法 vs 动态规划:如何选择最优解法》

【免费下载链接】LeetCode-Book 《剑指 Offer》 Python, Java, C++ 解题代码,LeetBook《图解算法数据结构》配套代码仓 【免费下载链接】LeetCode-Book 项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Book

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

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

抵扣说明:

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

余额充值