什么是动态规划

动态规划(Dynamic Programming,简称 DP)是一种解决复杂问题的算法思想,通过将问题分解为更小的子问题,并将子问题的解存储起来以避免重复计算,从而提高算法的效率。动态规划通常用于优化问题,即在给定的约束条件下,找到最优解。

动态规划的核心

动态规划中状态是核心,通常用一个或多个变量表示,即dp[i]或dp[i][j],然后用状态转移方程进行推导,但要注意初始状态和边界条件。

使用场景

动态规划适用于具有以下特征的问题:

  • 最优子结构:
    问题的最优解包含其子问题的最优解,即,可以通过子问题的最优解构造出原问题的最优解。
  • 重叠子问题:
    问题的递归求解过程中,存在大量的重复计算。动态规划通过存储子问题的解来避免重复计算。

常见应用

背包问题

  • 0-1背包
    dp[i][j] 表示前 i 种物品在容量为 j 的背包中能达到的最大价值
    dp[0][j] = 0:不选任何物品时,价值为0。
    dp[i][0] = 0:背包容量为0时,价值为0。
    状态转移方程:
    dp[i][j]=max(dp[i−1][j],dp[i−1][j−w[i]]+v[i])
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int knapsack_01(vector<int>& w, vector<int>& v, int W) {
vector<vector<int>> dp(w.size()+1, vector<int>(W+1, 0));

for (int i = 1; i <= w.size(); ++i) {
for (int j = 1; j <= W; ++j) {
if (j >= w[i-1]) {
dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i-1]] + v[i-1]);
} else {
dp[i][j] = dp[i-1][j];
}
}
}
return dp[w.size()][W];
}
  • 完全背包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int knapsack_complete(vector<int>& w, vector<int>& v, int W) {
vector<vector<int>> dp(w.size()+1, vector<int>(W+1, 0));

for (int i = 1; i <= w.size(); ++i) {
for (int j = 1; j <= W; ++j) {
if (j >= w[i-1]) {
dp[i][j] = max(dp[i-1][j], dp[i][j-w[i-1]] + v[i-1]);
} else {
dp[i][j] = dp[i-1][j];
}
}
}
return dp[w.size()][W];
}

可以优化

1
2
3
4
5
6
7
8
9
10
11
int knapsack_complete(vector<int>& w, vector<int>& v, int W) {
vector<int> dp(W + 1, 0);

for (int i = 0; i < w.size(); ++i) {
for (int j = w[i]; j <= W; ++j) {
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
}

return dp[W];
}

序列问题

  • 最长递增子序列(LIS)
    dp[i] 表示以第 i 个元素结尾的最长递增子序列的长度
    dp[i] = 1:每个元素自身可以看作一个长度为1的递增子序列。
    状态转移方程:
    dp[i]=max(dp[i],dp[j]+1)
1
2
3
4
5
6
7
8
9
int n = arr.size();
vector<int> dp(n, 1);
for (int i = 1; i < n; ++i) {
for (int j = 0; j < i; ++j) {
if (arr[j] < arr[i]) {
dp[i] = max(dp[i], dp[j] + 1);
}
}
}
  • 最长公共子序列(LCS)
    dp[i][j] 表示字符串 X 的前 i 个字符和字符串 Y 的前 j 个字符的最长公共子序列的长度。
    dp[0][j] = 0:空字符串与任何字符串的最长公共子序列长度为0。
    dp[i][0] = 0:空字符串与任何字符串的最长公共子序列长度为0。
    状态转移方程:
    如果 X[i−1]==Y[j−1]:
    dp[i][j]=dp[i−1][j−1]+1
    如果 X[i−1]!=Y[j−1]:
    dp[i][j]=max(dp[i−1][j],dp[i][j−1])
1
2
3
4
5
6
7
8
9
10
11
12
13
int m = X.size();
int n = Y.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 (X[i - 1] == Y[j - 1]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
}