一、简介
动态规划(Dynamic Programming,简称 DP)是一种通过将原问题分解为若干个子问题来求解最优化问题的算法思想。动态规划常常用于解决那些可以被分解为更小的重叠子问题的场景。
与分治法的区别在于,分治法会将问题分解成独立的子问题,而动态规划则是通过保存之前的计算结果来避免重复计算,从而提高效率。常见的问题有入门级:斐波那契数列、爬楼梯;进阶:背包问题、打家劫舍、股票问题、子序列问题。
动态规划的核心思想是“记忆化” — 将子问题的解存储起来,避免重复计算。常见的实现方式有“自顶向下”的递归(带备忘录)和“自底向上”的迭代方法。尽管动态规划在初学者眼中可能有些晦涩,但通过理解和掌握它的基本思想和解决步骤,我们将能够有效地解决很多复杂的优化问题。掌握动态规划的技巧,不仅能够提高算法能力,也能为解决实际问题提供强有力的工具。
接下来,我们将详细讲解如何使用动态规划解决问题,并且通过实际例子来理解这些步骤。
二、三大步骤
1、定义数组元素的含义(状态定义):需要明确你定义的数组中每个元素的含义。常见的做法是用一维或二维数组来存储问题的子问题解。例如,dp[i]
可能表示到达第 i
个状态的最优解。
2、找出数组元素之间的关系式(转移方程):通过问题的性质,推导出当前状态如何依赖于之前的状态。例如,dp[n] = dp[n-1] + dp[n-2]
表示某个问题的解由前两个状态的解来决定。
3、找出初始值:设定边界条件,通常是最小的子问题,如 dp[0]
或 dp[1]
,这些值是已知的或通过显式条件给出的。
4、返回值
三、案例详解
先看一下入门案例
3.1 斐波那契数列
问题描述:
斐波那契数 (通常用 F(n)
表示)形成的序列称为 斐波那契数列 。该数列由 0
和 1
开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给定 n
,请计算 F(n)
。
题目解析:
- 状态定义: 设 dp 为一维数组,其中 dp[i] 的值代表 斐波那契数列第 i 个数字 。
- 转移方程: dp[i+1]=dp[i]+dp[i−1] ,即对应数列定义 f(n+1)=f(n)+f(n−1) 。
- 初始状态: dp[0]=0, dp[1]=1 ,即初始化前两个数字。
- 返回值: dp[n] ,即斐波那契数列的第 n 个数字。
状态压缩:若新建长度为 n 的 dp 列表,则空间复杂度为 O(N) 。由于 dp 列表第 i 项只与第 i−1 和第 i−2 项有关,因此只需要初始化三个整形变量 sum, a, b ,利用辅助变量 sum 使 a,b 两数字交替前进即可 (具体实现见代码) 。节省了 dp 列表空间,因此空间复杂度降至 O(1) 。
实现代码:
class Solution {public int fib(int n) {// 初始化前两个斐波那契数int a = 0, b = 1, sum;// 通过迭代计算斐波那契数列for (int i = 0; i < n; i++) {sum = a + b; // 当前斐波那契数等于前两个数的和a = b; // 更新 a 为前一个斐波那契数b = sum; // 更新 b 为当前斐波那契数}return a; // 返回第 n 项,注意 i 从 0 开始,因此最终 a 存储的是 fib(n)}
}
3.2 爬楼梯问题
问题描述:
假设你正在爬楼梯。需要 n
阶你才能到达楼顶。
每次你可以爬 1
或 2
个台阶。你有多少种不同的方法可以爬到楼顶呢?
题目解析:
-
定义数组元素的含义:
dp[i]
表示跳到第i
级台阶的跳法总数。 -
关系式:可以从
i-1
或i-2
级台阶跳到i
级,因此:dp[i] = dp[i-1] + dp[i-2]
-
初始值:
dp[0] = 0
(没有台阶),dp[1] = 1
(只有1个台阶时,只有一种跳法)
同样进行状态压缩。
实现代码:
class Solution {public int climbStairs(int n) {// 初始值设置int p = 1, q = 1, r = 0;for (int i = 2; i <= n; i++) {r = p + q; // 当前楼梯的总方法数p = q; // p 更新为之前的 qq = r; // q 更新为当前的 r}return q; // 返回最终的爬楼梯方法数}//优化前// if (n <= 1) return n;// int dp[n + 1];// dp[0] = 0; dp[1] = 1;// for (int i = 2; i <= n; i++) {// dp[i] = dp[i - 1] + dp[i - 2];// }// return dp[n];
}