【数据结构与算法Trip第5站】动态规划
动态规划详解(含C++代码实现)
什么是动态规划?
动态规划(Dynamic Programming,简称DP)是一种解决复杂问题的算法思想,它通过将问题分解为相互重叠的子问题,并存储子问题的解(记忆化)来避免重复计算,从而提高效率。
动态规划的核心思想
- 最优子结构:问题的最优解包含其子问题的最优解
- 重叠子问题:不同的决策序列会到达相同的子问题
- 状态转移方程:定义如何从一个状态转移到另一个状态
动态规划的两种实现方式
1. 自顶向下(记忆化递归)
#include <iostream>
#include <vector>
#include <unordered_map>
using namespace std;// 斐波那契数列 - 记忆化递归
unordered_map<int, int> memo;int fib(int n) {if (n <= 1) return n;if (memo.find(n) != memo.end()) return memo[n];memo[n] = fib(n - 1) + fib(n - 2);return memo[n];
}
2. 自底向上(迭代)
// 斐波那契数列 - 迭代
int fib_iterative(int n) {if (n <= 1) return n;vector<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];
}
经典动态规划问题示例
1. 爬楼梯问题
问题描述:每次可以爬1或2个台阶,爬到n阶有多少种方法?
int climbStairs(int n) {if (n <= 2) return n;vector<int> dp(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];
}// 空间优化版本
int climbStairs_optimized(int n) {if (n <= 2) return n;int prev1 = 1, prev2 = 2;for (int i = 3; i <= n; i++) {int current = prev1 + prev2;prev1 = prev2;prev2 = current;}return prev2;
}
2. 0-1背包问题
问题描述:给定物品的重量和价值,在不超过背包容量的情况下最大化价值。
int knapsack(int W, vector<int>& weights, vector<int>& values) {int n = weights.size();vector<vector<int>> dp(n + 1, vector<int>(W + 1, 0));for (int i = 1; i <= n; i++) {for (int w = 1; w <= W; w++) {if (weights[i - 1] <= w) {dp[i][w] = max(dp[i - 1][w], // 不选当前物品dp[i - 1][w - weights[i - 1]] + values[i - 1] // 选当前物品);} else {dp[i][w] = dp[i - 1][w];}}}return dp[n][W];
}// 空间优化版本(一维数组)
int knapsack_optimized(int W, vector<int>& weights, vector<int>& values) {int n = weights.size();vector<int> dp(W + 1, 0);for (int i = 0; i < n; i++) {for (int w = W; w >= weights[i]; w--) {dp[w] = max(dp[w], dp[w - weights[i]] + values[i]);}}return dp[W];
}
3. 最长公共子序列(LCS)
问题描述:找出两个序列的最长公共子序列的长度。
int longestCommonSubsequence(string text1, string text2) {int m = text1.length(), n = text2.length();vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));for (int i = 1; i <= m; i++) {for (int j = 1; j <= n; j++) {if (text1[i - 1] == text2[j - 1]) {dp[i][j] = dp[i - 1][j - 1] + 1;} else {dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);}}}return dp[m][n];
}
动态规划解题步骤
- 定义状态:明确dp数组的含义
- 确定状态转移方程:如何从已知状态推导出新状态
- 初始化基础情况:最简单的子问题的解
- 确定计算顺序:确保计算当前状态时所需的状态已计算
- 返回结果:根据问题要求返回相应的结果
复杂度分析
- 时间复杂度:通常为O(状态数 × 每个状态的计算时间)
- 空间复杂度:通常为O(状态数),可通过优化降低
适用场景
动态规划适用于具有以下特征的问题:
- 求最优解(最大值、最小值等)
- 问题可以分解为相互重叠的子问题
- 具有最优子结构性质
总结
动态规划是一种强大的算法设计技术,通过练习各种经典问题(如背包问题、最长递增子序列、编辑距离等),可以更好地掌握这种思想。关键是多实践,理解状态定义和转移方程的设计。