动态规划
动态规划基础
动态规划(Dynamic Programming)是一种分阶段求解问题的思想,它通过将原问题分解为相对简单的子问题的方式来求解复杂问题。
动态规划适用条件
- 最优子结构:问题的最优解包含子问题的最优解
- 重叠子问题:递归算法会重复求解相同的子问题
Java实现案例
1. 斐波那契数列
递归解法(非动态规划)
public int fib(int n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}
动态规划解法(自底向上)
public int fib(int n) {
if (n <= 1) return n;
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];
}
优化空间复杂度版本
public int fib(int n) {
if (n <= 1) return n;
int prev1 = 1; // f(n-1)
int prev2 = 0; // f(n-2)
for (int i = 2; i <= n; i++) {
int current = prev1 + prev2;
prev2 = prev1;
prev1 = current;
}
return prev1;
}
2. 爬楼梯问题
问题描述:每次可以爬1或2个台阶,有多少种不同的方法可以爬到第n阶?
动态规划解法
public int climbStairs(int n) {
if (n == 1) return 1;
int[] dp = new int[n + 1];
dp[1] = 1;
dp[2] = 2;
for (int i = 3; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
空间优化版本
public int climbStairs(int n) {
if (n == 1) return 1;
int first = 1;
int second = 2;
for (int i = 3; i <= n; i++) {
int third = first + second;
first = second;
second = third;
}
return second;
}
3. 最长公共子序列(LCS)
问题描述:给定两个字符串,找出它们最长的公共子序列的长度。
动态规划解法
public int longestCommonSubsequence(String text1, String text2) {
int m = text1.length(), n = text2.length();
int[][] dp = new int[m + 1][n + 1];
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
}
return dp[m][n];
}
4. 0-1背包问题
问题描述:给定一组物品,每种物品都有自己的重量和价值,在限定的总重量内,如何选择物品使得总价值最大。
动态规划解法
public int knapsack(int W, int[] weights, int[] values, int n) {
int[][] dp = new int[n + 1][W + 1];
for (int i = 1; i <= n; i++) {
for (int w = 1; w <= W; w++) {
if (weights[i - 1] <= w) {
dp[i][w] = Math.max(
values[i - 1] + dp[i - 1][w - weights[i - 1]],
dp[i - 1][w]
);
} else {
dp[i][w] = dp[i - 1][w];
}
}
}
return dp[n][W];
}
空间优化版本(一维数组)
public int knapsack(int W, int[] weights, int[] values, int n) {
int[] dp = new int[W + 1];
for (int i = 0; i < n; i++) {
for (int w = W; w >= weights[i]; w--) {
dp[w] = Math.max(dp[w], values[i] + dp[w - weights[i]]);
}
}
return dp[W];
}
动态规划解题步骤总结
- 定义状态:明确dp数组或变量表示的含义
- 确定状态转移方程:找出如何从子问题的解得到当前问题的解
- 初始化边界条件:确定最简单子问题的解
- 确定计算顺序:通常是自底向上或自顶向下
- 考虑空间优化:看是否能减少空间复杂度
通过以上案例和步骤,可以更好地理解和应用动态规划算法解决实际问题。