梦想不会自己发光,真正闪耀的是那个为梦狂奔的你。献给知行的孩子们!(Eric.He著)
动态规划的本质:它是一种 “以空间换时间”的优化算法,核心是把复杂问题拆解为多个重叠的子问题,通过记录子问题的最优解(状态),避免重复计算,最终由子问题的最优解推导出原问题的最优解。
计数型问题是求解决问题的所有合法方案数,比如 “有多少种方法到达终点”“有多少种组合满足条件”。这类问题同样存在重叠子问题,DP 通过记录 “到达某一状态的方案数”,由子状态的方案数累加得到当前状态的方案数,本质是 “最优子结构” 的延伸(方案数的累加性)。
问题描述:
算法解析:
dp 数组计算过程:
输入:m = 3, n = 7
#include <iostream>
#include <vector>
using namespace std;
int uniquePaths(int m, int n) {
// 定义二维DP数组,dp[i][j]表示从(0,0)到(i,j)的路径数
vector<vector<int>> dp(m, vector<int>(n));
// 填充第一列
for (int i = 0; i < m; ++i) {
dp[i][0] = 1;
}
// 填充第一行
for (int j = 0; j < n; ++j) {
dp[0][j] = 1;
}
// 填充非边界位置(i>0且j>0)
for (int i = 1; i < m; ++i) {
for (int j = 1; j < n; ++j) {
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
return dp[m-1][n-1];
}
int main() {
cout << "m=3, n=7: " << uniquePaths(3, 7) << endl; // 输出 28
cout << "m=3, n=2: " << uniquePaths(3, 2) << endl; // 输出 3
cout << "m=7, n=3: " << uniquePaths(7, 3) << endl; // 输出 28
return 0;
}
m=3, n=7: 28
m=3, n=2: 3
m=7, n=3: 28
问题描述:
算法解析:
dp 数组计算过程:
输入:grid = [[0,0,0],[0,1,0],[0,0,0]]
#include <iostream>
#include <vector>
using namespace std;
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
if (obstacleGrid.empty() || obstacleGrid[0].empty()) return 0;
int m = obstacleGrid.size(); // 行数
int n = obstacleGrid[0].size(); // 列数
// 起点有障碍物,直接返回0
if (obstacleGrid[0][0] == 1) return 0;
// 定义DP数组,dp[i][j]表示从(0,0)到(i,j)的有效路径数
vector<vector<int>> dp(m, vector<int>(n, 0));
dp[0][0] = 1; // 初始化起点
// 填充第一行(只能从左边来)
for (int j = 1; j < n; ++j) {
// 无障碍物 且 左边位置可达 → 路径数=左边路径数;否则为0
if (obstacleGrid[0][j] == 0 && dp[0][j-1] == 1) {
dp[0][j] = 1;
}
}
// 填充第一列(只能从上面来)
for (int i = 1; i < m; ++i) {
// 无障碍物 且 上边位置可达 → 路径数=上边路径数;否则为0
if (obstacleGrid[i][0] == 0 && dp[i-1][0] == 1) {
dp[i][0] = 1;
}
}
// 填充其余位置
for (int i = 1; i < m; ++i) {
for (int j = 1; j < n; ++j) {
// 有障碍物 → 路径数为0
if (obstacleGrid[i][j] == 1) {
dp[i][j] = 0;
} else {
// 无障碍物 → 路径数=上边+左边
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
}
return dp[m-1][n-1];
}
int main() {
// 测试用例1:输出 2
vector<vector<int>> grid1 = {{0,0,0},{0,1,0},{0,0,0}};
cout << "Test Case 1: " << uniquePathsWithObstacles(grid1) << endl;
// 测试用例2:输出 0(终点有障碍物)
vector<vector<int>> grid2 = {{0,0},{1,1},{0,0}};
cout << "Test Case 2: " << uniquePathsWithObstacles(grid2) << endl;
// 测试用例3:输出 1(单行无障碍物)
vector<vector<int>> grid3 = {{0,0,0,0}};
cout << "Test Case 3: " << uniquePathsWithObstacles(grid3) << endl;
return 0;
}
Test Case 1: 2
Test Case 2: 0
Test Case 3: 1
问题描述:
算法解析:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int integerBreak(int n) {
// dp[i] 表示拆分整数i能得到的最大乘积
vector<int> dp(n + 1, 0);
// 初始条件:2只能拆成1+1,乘积为1
dp[2] = 1;
// 从3开始计算每个数的最大乘积
for (int i = 3; i <= n; ++i) {
// 遍历所有可能的拆分点j(1 <= j < i)
for (int j = 1; j < i; ++j) {
// 两种情况:j*(i-j)(不拆i-j) 或 j*dp[i-j](拆i-j)
int current = max(j * (i - j), j * dp[i - j]);
// 更新dp[i]为所有可能中的最大值
dp[i] = max(dp[i], current);
}
}
return dp[n];
}
int main() {
int n1 = 2, n2 = 10;
cout << "n = " << n1 << " 时,最大乘积:" << integerBreak(n1) << endl; // 输出1
cout << "n = " << n2 << " 时,最大乘积:" << integerBreak(n2) << endl; // 输出36
return 0;
}
n = 2 时,最大乘积:1
n = 10 时,最大乘积:36
问题描述:
算法解析:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int numDecodings(string s) {
int n = s.size();
// 空字符串或首字符为0,直接返回0
if (n == 0 || s[0] == '0') return 0;
// dp[i] 表示前i个字符的解码方式数
vector<int> dp(n + 1, 0);
dp[0] = 1; // 边界条件:空字符串有1种解码方式
dp[1] = 1; // 第一个字符非0,初始为1
for (int i = 2; i <= n; ++i) {
// 情况1:单独解码第i个字符(对应s[i-1])
if (s[i-1] != '0') {
dp[i] += dp[i-1];
}
// 情况2:解码第i-1和i个字符组成的两位数(对应s[i-2]和s[i-1])
int two_digit = stoi(s.substr(i-2, 2)); // 提取两位数字
if (two_digit >= 10 && two_digit <= 26) {
dp[i] += dp[i-2];
}
}
return dp[n];
}
int main() {
cout << numDecodings("12") << endl; // 输出2
cout << numDecodings("226") << endl; // 输出3
cout << numDecodings("06") << endl; // 输出0
cout << numDecodings("10") << endl; // 输出1(只有10一种方式)
return 0;
}
2
3
0
1