1. 动态规划的思想
动态规划的思想是通过把原问题分解成相对简单的子问题的方式来解决复杂问题的方法。
2. 适应场景
动态规划适用于具有最优子结构和重叠子问题的问题。
最优子结构是指问题的最优解可以由子问题的最优解推导得到,重叠子问题是指不同的子问题可能包含相同的更小的子问题。
3. 动态规划的一般步骤
动态规划的一般步骤是:
- 定义状态:用一个或多个变量来描述问题的不同阶段或状态。
- 确定状态转移方程:根据状态之间的关系,找出从一个状态到另一个状态的转移规律,通常是一个递推或递归的公式。
- 确定边界条件:确定初始状态和终止状态,以及可能存在的特殊情况。
- 选择合适的存储结构:根据状态的数量和复杂度,选择合适的一维或多维数组或其他数据结构来存储子问题的解,以便于后续的计算和引用。
- 按照一定的顺序求解状态:根据状态转移方程和边界条件,按照自底向上或自顶向下的顺序,逐个求解每个状态的值,并保存在存储结构中。
- 构造最优解:根据存储结构中的值,从终止状态开始,逆向推导出最优解,或者直接输出最终状态的值作为最优解。
4. 动态规划求解 示例
动态规划的思想可以用于解决很多经典的问题,例如斐波那契数列、背包问题、最长公共子序列、最短路径等。
以下是一个使用动态规划求解斐波那契数列第n项的例子:
// 定义状态:f(n)表示斐波那契数列第n项 // 确定状态转移方程:f(n) = f(n-1) + f(n-2) // 确定边界条件:f(0) = 0, f(1) = 1 // 选择合适的存储结构:用一个一维数组dp来存储f(n)的值 // 按照一定的顺序求解状态:从小到大依次求解f(0), f(1), ..., f(n),并保存在dp中 // 构造最优解:返回dp[n]作为最终答案 static int fib(int n) { if (n < 0) return -1; // 输入非法 if (n == 0) return 0; // 特殊情况 int[] dp = new int[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]; // 最优解 }
5.动态规划的优点
动态规划的优点是
- 可以有效地减少重复计算,提高算法的效率,
- 同时可以得到很多有用的中间结果,方便构造最优解。
动态规划的缺点是
- 需要额外的空间来存储子问题的解,可能占用较多的内存,
- 而且有些问题难以找到合适的状态和状态转移方程,需要一定的技巧和经验。