遇到矩阵,网络,字符串间的比较题,单序列(一维)动规解决不了的情况下,就需要考虑双序列(二维)动规。
例七 路径总数
机器人在(m*n)网格的左上角,可以向下,向右走,走到网格的右下角,有多少种可能的路径。
状态:F(i,j):走到(i,j)时有多少种可能的路径
递推:中间:F(i,j) = F(i-1,j)+F(i,j-1)
初始化:
第0列:F(i,0) = 1;
第0行:F(0,i) = 1;
返回结果:
F(m-1)(n-1)
#include<iostream>
#include<vector>
using namespace std;
int uniquePaths(int m, int n)
{
if (m < 1 || n < 1)
return 0;
//初始化 F(0,i) = F(i,0) = 1;
vector<vector<int>> path(m, vector<int>(n, 1));
//递推
for (int i = 1; i < m; i++)
{
for (int j = 1; j < n; j++)
{
path[i][j] = path[i - 1][j] + path[i][j - 1];
}
}
return path[m - 1][n - 1];
}
int main()
{
cout << uniquePaths(5,5) << endl;
system("pause");
return 0;
}
例八 路径总数之路径上有坑
机器人走网格,网格上有坑,不能走
【0,0,0】
【0,1,0】
【0,0,0】
1不能走,共有两条路径
状态:F(i,j):从(0,0)走到(i,j)时有多少种可能的路径
递推:中间:F(i,j):if (i,j)==1 —>F(i,j) = 0
else F(i,j)= F(i-1,j)+F(i,j-1)
初始化:同样要判断(i,j)是否为1,如果为1,后面就都去不了了,因为只能往右,往下走
第0列:F(i,0) = 1;
第0行:F(0,i) = 1;
返回结果:
F(m-1)(n-1)
#include<iostream>
#include<vector>
using namespace std;
int uniquePathsWithObstacles(vector<vector<int>> & obstacleGrid)
{
if (obstacleGrid.empty())
return 0;
int m = obstacleGrid.size();
int n = obstacleGrid[0].size();
vector<vector<int>> paths(m, vector<int>(n, 0));
//初始化 F(0,0)
//第一列初始化
for (int i = 0; i < m; i++)
{
if (obstacleGrid[i][0])
break;
else
paths[i][0] = 1;
}
//第一行
for (int i = 0; i < n; i++)
{
if (obstacleGrid[0][i])
break;
else
paths[0][i] = 1;
}
//递推
for (int i = 1; i < m; i++)
{
for (int j = 1; j < n; j++)
{
if (obstacleGrid[i][j])
paths[i][j] = 0;
else
paths[i][j] = paths[i - 1][j] + paths[i][j - 1];
}
}
return paths[m - 1][n - 1];
}
int main()
{
vector<vector<int>> obstaclesGrid = { {0,0,0},{0,1,0},{0,0,0} };
cout << uniquePathsWithObstacles(obstaclesGrid) << endl;
system("pause");
return 0;
}
例九 矩阵最小路径和
一个非负矩阵,从左上角到右下角的最小路径和
状态:F(i,j):从(0,0)到(i,j)的最小路径和
递推:
第一行:F(0,i)=F(0,i-1)+(i,j)
第一列:F(i,0)=F(i-1,0)+(i,j)
其他位置:F(i,j) = min(F(i-1,j), F(i,j-1))+ (i,j)
初始化:F(0,0) = (0,0)
返回结果:F(m-1,n-1)
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int FindMinBoard(vector<vector<int>> & board)
{
if (board.empty())
return 0;
int m = board.size();
int n = board[0].size();
vector<vector<int>> min_sum(m, vector<int>(n, 0));
//初始化
min_sum[0][0] = board[0][0];
//第一列递推
for (int i = 1; i < m; i++)
min_sum[i][0] = board[i][0]+min_sum[i-1][0];
//第一行递推
for (int i = 1; i < n; i++)
min_sum[0][i] = board[0][i]+min_sum[0][i-1];
//递推
for (int i = 1; i < m; i++)
{
for (int j = 1; j < n; j++)
{
min_sum[i][j] = min(min_sum[i - 1][j], min_sum[i][j - 1]) + board[i][j];
}
}
//返回结果
return min_sum[m - 1][n - 1];
}
int main()
{
vector<vector<int>> board{ {1,2,3},{4,5,6},{7,8,9} };
cout << FindMinBoard(board) << endl;
system("pause");
return 0;
}
例十 回文串分割
有一个字符串s,把s分割成一系列的子串,分割的每一个子串都为回文串,返回最小的分割次数(回文串左右对称)。
状态:F(i):前i个字符的最小分割数
递推:j<i && F(i):min(F(j)+1,F(i)) && substr[j+1,i)是回文串
初始化:F(i)=i-1,F(0)=-1
返回结果:F(i)
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;
bool isPal(string s, int i, int j)
{
while (i < j)
{
if (s[i] != s[j])
return false;
i++;
j--;
}
return true;
}
int MinOfCut(string & s)
{
if (s.empty())
return -1;
int n = s.size();
vector<int> cut(n+1,0);
//初始化 F[i] = i-1
for (int i = 0; i < n + 1; i++)
cut[i] = i - 1;//最大分割数
//递推
for (int i = 1; i < n + 1; i++)
{
for (int j = 0; j < i; j++)
{
//j<i
//substr[j+1,i)是回文串
if (isPal(s, j, i - 1))
{
//F(i) = min(F(j)+1,F(i))
cut[i] = min(cut[j] + 1, cut[i]);
}
}
}
return cut[n];
}
int main()
{
string s("aaabaa");
cout << MinOfCut(s) << endl;
system("pause");
return 0;
}
用动态规划来判断一个字符区间的子串是否为回文串的版本:
状态:F[i,j]是否为回文串
递推:F(i,j):s[i] == s[j] && F(i+1,j-1)-------从后面做起始
初始化:单字符都是回文的,F(i,i)=true
返回结果:返回F(i,j)矩阵,只更新一半(i<=j)
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;
vector<vector<bool>> is_PalDP(string s)
{
int n = s.size();
vector<vector<bool>> is_pal(n, vector<bool>(n, false));
for (int i = n - 1; i >= 0; i--)
{
for (int j = i; j < n; j++)
{
if (j == i)
is_pal[i][j] = true;//F[i][i] = true 初始化
else if (j == i + 1)//连续两个字符
is_pal[i][j] = (s[i] == s[j]);
else //j>i+1
is_pal[i][j] = (s[i] == s[j] && is_pal[i + 1][j - 1]);
}
}
return is_pal;
}
int MinOfCut(string & s)
{
if (s.empty())
return -1;
int n = s.size();
vector<int> cut(n + 1, 0);
//初始化 F[i] = i-1
for (int i = 0; i < n + 1; i++)
cut[i] = i - 1;
vector<vector<bool>> is_pal = is_PalDP(s);
//递推
for (int i = 1; i < n + 1; i++)
{
for (int j = 0; j < i; j++)
{
//j<i
//substr[j+1,i)是回文串
if (is_pal[j][i - 1])
{
//F(i) = min(F(j)+1,F(i))
cut[i] = min(cut[j] + 1, cut[i]);
}
}
}
return cut[n];
}
int main()
{
string s("aaabaa");
cout << MinOfCut(s) << endl;
system("pause");
return 0;
}
简单的动态规划问题,状态,状态递推和状态初始化都比较直观。对于复杂的动态规划问题,状态,状态递推和状态初始化都比较隐含,需要仔细推敲。尤其是状态递推可能需要额外的辅助判断条件才能达成。
例十一 编辑距离
从一个字符串word1变到另一个字符串word2的最小操作数(插入一个字符、删除一个字符、替换一个字符)
状态:把word1的前i个字符转成word2的前j个字符
递推:
替换:if(word1[i] == word2[j]) ---->F(i,j) = F(i-1,j-1) else : F(i,j) = F(i-1,j-1)+1
插入:少一个才插入一个,F(i-1,j)=F(i-1,j-1)+1 F(i,j)=F(i,j-1)+1; xa–>xav
删除:多一个才删除一个,F(i,j-1)=F(i-1,j-1)+1 F(i,j)=F(i-1,j)+1; xat—>xa
递推公式:F(i,j)=min{F(i,j-1)+1,F(i-1,j)+1, F(i-1,j-1)+((w1[i]==w2[j])?0:1)}
初始化:空状态–空串
F(i,0)=i 字符串转化为空串,删除操作
F(0,i)=i 空串转化为字符串,插入操作
返回结果:F(m,n)
#include<iostream>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;
int minDistance(string word1, string word2)
{
if (word1.empty() || word2.empty())
return max(word1.size(), word2.size());
int m = word1.size();
int n = word2.size();
vector<vector<int>> min_dis(m + 1, vector<int>(n + 1, 0));
//初始化 F(i,0) F(0,i)
for (int i = 0; i < m + 1; i++)
min_dis[i][0] = i;
for (int i = 0; i < n + 1; i++)
min_dis[0][i] = i;
//递推
for (int i = 1; i < m + 1; i++)
{
for (int j = 1; j < n + 1; j++)
{
//F(i,j)=min{F(i,j-1)+1,F(i-1,j)+1, F(i-1,j-1)+((w1[i]==w2[j])?0:1)} 插入 删除 替换
min_dis[i][j] = min(min_dis[i - 1][j], min_dis[i][j - 1]) + 1;
if (word1[i - 1] == word2[j - 1])
min_dis[i][j] = min(min_dis[i][j], min_dis[i - 1][j - 1]);
else
min_dis[i][j] = min(min_dis[i][j], min_dis[i - 1][j - 1]+1);
}
}
//返回结果
return min_dis[m][n];
}
int main()
{
cout << minDistance("haha", "hehe") << endl;
system("pause");
return 0;
}
例十二 不同子序列
给定两个字符串S和T,求S有多少个不同的子序列与T相同,子序列可以不连续,但是相对位置不能变