动态规划
动态规划
回顾分治、回溯、递归学习动态规划
递归模板
java代码
public void recur(int level, int param) {
// terminator
if (level > MAX_LEVEL) {
// process result
return;
}
// process current logic
process(level, param);
// drill down
recur( level: level + 1, newParam);
// restore current status
}
Python代码
def recursion(level, param1, param2, ...):
# recursion terminator
if level > MAX_LEVEL:
process_result
return
# process logic in current level
process(level, data...)
# drill down
self.recursion(level + 1, p1, ...)
# reverse the current level status if needed
分治代码模板
def divide_conquer(problem, param1, param2, ...):
# recursion terminator
if problem is None:
print_result
return
# prepare data
data = prepare_data(problem)
subproblems = split_problem(problem, data)
# conquer subproblems
subresult1 = self.divide_conquer(subproblems[0], p1, ...)
subresult2 = self.divide_conquer(subproblems[1], p1, ...)
subresult3 = self.divide_conquer(subproblems[2], p1, ...)
…
# process and generate the final result
result = process_result(subresult1, subresult2, subresult3, …)
# revert the current level states
感触
- 人肉递归低效、很累
- 找到最近最简方法,将其拆解成可重复解决的问题(寻找重复子问题)
- 数学归纳法思维(抵制人肉递归的诱惑)
本质:寻找重复性——>计算机指令集
动态规划 Dynamic programming
Divide & Conquer + Optimal substructure
分治+最优子结构
关键点
- 动态规划和递归或者分治没有根本上的区别(关键看有无最优的子结构)
- 共性:找到重复子问题
- 差异性:最优子结构,中途可以淘汰次优解
实战一:斐波那契数列
int fib (int n)
{
if (n <= 0)
{
return 0;
}
else if (n == 1)
{
return 1;
}
else
{
return fib (n - 1) + fib (n - 2);
}
}
int fib(int n)
{
return n<=1?n:fib(n-1)+fib(n-2);
}
实战例题二:路径计数
class Solution {
public:
int uniquePaths(int m, int n) {
int dp[m][n];
for (int i = 0; i < m; i++)
{
dp[i][0]=1;
}
for (int j = 0; j < n; j++)
{
dp[0][j]=1;
}
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];
}
};
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
int m=obstacleGrid.size();
int n=obstacleGrid[0].size();
vector<vector<long int>>dp(m,vector<long int>(n,0));
dp[0][0]=obstacleGrid[0][0]==1?0:1;
for (int i = 0; i < m; i++)
{
for(int j=0;j<n;j++)
{
if(i==0&&j!=0) //第一行的时候
{
if(obstacleGrid[i][j]!=1)
{
dp[i][j]=dp[i][j-1];
}
else
{
dp[i][j]=0;
}
}
else if(j==0&&i!=0)//第一列的时候
{
if(obstacleGrid[i][j]!=1)
{
dp[i][j]=dp[i-1][j];
}
else
{
dp[i][j]=0;
}
}
else if(i!=0&&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];
}
};
动态规划关键点
- 最优子结构 opt[n]=best_of(opt[n-1],opt[n-2],……)
- 存储中间状态:opt[i]
- 递推公式(状态转移方程)
实战例题三:最长公共子序列
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
const int rows = text1.size();
const int cols = text2.size();
vector<vector<int>> dp(rows, vector<int>(cols, 0));
// 求解边界 BEGIN
if (text1[0] == text2[0])
dp[0][0] = 1;
for (int i = 1; i < rows; i++) {
dp[i][0] = dp[i - 1][0];
if (text1[i] == text2[0]) {
dp[i][0] = 1;
}
}
for (int j = 1; j < cols; j++) {
dp[0][j] = dp[0][j - 1];
if (text1[0] == text2[j]) {
dp[0][j] = 1;
}
}
// 求解边界 END
for (int i = 1; i < rows; i++)
for (int j = 1; j < cols; j++) {
if (text1[i] == text2[j]) { // 遇到匹配字符
dp[i][j] = dp[i - 1][j - 1] + 1;
} else { // 不匹配时,继承左上中的较大值
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
}
}
return dp[rows - 1][cols - 1];
}
};
小结
- 打破自己的思维惯性,形成机器思维
- 理解复杂逻辑的关键
- 也是职业进阶的要领根本