动态规划经典问题学习笔记
动态规划概述
动态规划(Dynamic Programming,简称DP)是一种通过将原问题分解为子问题,并存储子问题的解来避免重复计算的优化技术。它通常用于解决具有重叠子问题和最优子结构性质的问题。
动态规划的核心思想
- 分解问题:将原问题分解为更小的子问题。
- 存储子问题解:将子问题的解存储在表格(通常是数组或矩阵)中,避免重复计算。
- 构建最优解:从子问题的最优解构建原问题的最优解。
动态规划的适用条件
- 重叠子问题:问题的递归解包含重复的子问题。
- 最优子结构:问题的最优解可以由子问题的最优解有效地构建。
经典问题列表
1. 爬楼梯问题
问题描述
假设你正在爬楼梯,需要 n
阶才能到达楼顶。每次你可以爬 1
或 2
个台阶,有多少种不同的方法可以爬到楼顶?
解题思路
- 递推关系:设
f(n)
为爬到第n
阶的方法数。由于最后一步只能是爬1
阶或2
阶,因此f(n) = f(n-1) + f(n-2)
- 边界条件:
f(1) = 1
(1阶只有1种方法),f(2) = 2
(2阶有两种方法:1+1 或 2)
完整代码
#include <iostream>
using namespace std;int climbStairs(int n) {if (n <= 2) {return n;}int a = 1, b = 2;for (int i = 3; i <= n; i++) {int c = a + b;a = b;b = c;}return b;
}int main() {int n = 5;cout << "爬" << n << "阶楼梯的方法数:" << climbStairs(n) << endl; // 输出:8return 0;
}
2. 最长公共子序列问题
问题描述
给定两个字符串 text1
和 text2
,返回它们的最长公共子序列的长度。如果不存在公共子序列,返回 0。
子序列是指在保持相对顺序的情况下,可以通过删除一些字符(也可以不删除)而不改变其余字符顺序的序列。
解题思路
- 动态规划定义:设
dp[i][j]
表示text1[0...i-1]
(前i
个字符)和text2[0...j-1]
(前j
个字符)的最长公共子序列长度。 - 递推关系:
- 若
text1[i-1] == text2[j-1]
(当前字符相同),则dp[i][j] = dp[i-1][j-1] + 1
。 - 若字符不同,则
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
- 若
- 边界条件:
dp[0][j] = 0
(text1
为空),dp[i][0] = 0
(text2
为空)
完整代码
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;int lCS(string text1, string text2) {int m = text1.size();int n = text2.size();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];
}int main(){string text1 = "abcde";cout << "第一个字符串为:" << text1 << endl;string text2 = "ace";cout << "第二个字符串为:" << text2 << endl;cout << "两个字符串的最长公共子序列长度为:" << lCS(text1, text2) << endl;return 0;
}
3. 编辑距离问题
问题描述
给定两个单词 word1
和 word2
,计算将 word1
转换成 word2
所使用的最少操作数。
允许的操作有:插入一个字符、删除一个字符、替换一个字符。
解题思路
- 动态规划定义:设
dp[i][j]
表示将word1[0...i-1]
(前i
个字符)转换为word2[0..j-1]
(前j
个字符)的最少操作数。 - 递推关系:
- 若
word1[i-1] == word2[j-1]
(当前字符相同),则无需操作:dp[i][j] = dp[i-1][j-1]
- 若字符不同,则取以下三种操作的最小值+1:
- 替换:
dp[i-1][j-1] + 1
- 删除:
dp[i-1][j] + 1
- 插入:
dp[i][j-1] + 1
- 替换:
- 若
- 边界条件:
dp[0][j] = j
(word1
为空,需插入j
个字符)dp[i][0] = i
(word2
为空,需删除i
个字符)
完整代码
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;int minDistance(string word1, string word2) {int m = word1.size();int n = word2.size();vector<vector<int> > dp(m + 1, vector<int>(n + 1, 0));// 初始化边界for (int i = 0; i <= m; i++) {dp[i][0] = i;}for (int j = 0; j <= n; j++) {dp[0][j] = j;}// 填充DP表for (int i = 1; i <= m; i++) {for (int j = 1; j <= n; j++) {if (word1[i - 1] == word2[j - 1]) {dp[i][j] = dp[i - 1][j - 1];} else {dp[i][j] = min(min(dp[i - 1][j - 1] + 1, // 替换dp[i - 1][j] + 1), // 删除dp[i][j - 1] + 1 // 插入);}}}return dp[m][n];
}int main() {string word1 = "horse";cout << word1 << endl;string word2 = "ros";cout << word2 << endl;cout << "最少编辑次数:" << minDistance(word1, word2) << endl; // 输出:3return 0;
}
4. 0/1背包问题
问题描述
有 n
件物品和一个容量为 capacity
的背包。第 i
件物品的重量为 weight[i]
,价值为 value[i]
。每件物品只能选择一次(要么放入,要么不放入),求背包能容纳的最大总价值。
解题思路
- 动态规划定义:设
dp[i][j]
表示前i
件物品中,选出总重量不超过j
的物品的最大价值. - 递推关系:
- 若不选第
i
件物品:dp[i][j] = dp[i-1][j]
- 若选第
i
件物品(需满足weight[i-1] ≤ j
):dp[i][j] = dp[i-1][j - weight[i-1]] + value[i-1]
- 取两者的最大值:
dp[i][j] = max
- 若不选第
- 边界条件:
dp[0][j] = 0
(前0件物品,价值为0)dp[i][0] = 0
(背包容量为0,价值为0)
完整代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;int knapsack01(int capacity, vector<int> & weight, vector<int>& value) {int n = weight.size();vector<vector<int> > dp(n + 1, vector<int>(capacity + 1, 0));for (int i = 1; i <= n; i++) {int w = weight[i - 1];int v = value[i - 1];for (int j = 1; j <= capacity; j++) {if (w > j) {dp[i][j] = dp[i - 1][j];} else {dp[i][j] = max(dp[i - 1][j],dp[i - 1][j - w] + v);}}}return dp[n][capacity];
}int main() {int capacity = 4;vector<int> weight = {2, 1, 3};vector<int> value = {4, 2, 3};cout << "背包最大价值:" << knapsack01(capacity, weight, value) << endl;return 0;
}