6专题总结-动态规划dynamic programming
专题6--动态规划
1、动态规划基础知识
什么情况下可能是动态规划?满足下面三个条件之一:
1. Maximum/Minimum -- 最大最小,最长,最短;写程序一般有max/min。
2. Yes/No----是否可行;写程序一般有||。
3. Count(*)--数方案的个数,比如有多少条路径这种。初始化0个的情况下,初始化为1,联想组合数学里面0! = 1。
则 “极有可能”是使用动态规划求解。
什么情况下可能不是动态规划?
1)如果题目需要求出所有 “具体 ”的方案而非方案 “个数 ”;
2)输入数据是一个 “集合 ”而不是 “序列 ”。(区分就是调整元素顺序看是否对求解有影响)
动态规划的4点要素:
1) 状态 State
灵感,创造力,存储小规模问题的结果
a) 最优解/Maximum/Minimum
b) Yes/No
c) Count(*)
2) 方程 Function
状态之间的联系,怎么通过小的状态,来求得大的状态
3) 初始化 Intialization
最极限的小状态是什么, 起点
4) 答案 Answer
最大的那个状态是什么,终点
动态规划有四类:
1. Matrix DP (15%)
2. Sequence (40%)
3. Two Sequences DP (40%)
*4. Others (5%)
动态规划就是 *解决了重复计算 * 的搜索。
动态规划的实现方式:
1. 记忆化搜索;
2. 循环。
循环求解更 *正规 *,存在空间优化的可能,大多数面试官可以接受;(滚动数组)
记忆化搜索空间耗费更大,但思维难度小,编程难度小,时间效率很多情况下更高,少数有 *水平 *的面试官可以接受。
动态规划时间复杂度分析就是看有几个for循环。
3、Matrix DP
state: f[x][y] 表示我从起点走到坐标 x,y……
function: 研究走到 x,y这 个点之前的一步
intialize: 起点
answer: 终点
小技巧:intialize: f[0][0] = A[0][0]
// f[i][0] = sum(0,0 -> i,0)
// f[0][i] = sum(0,0 -> 0,i)
初始化的时候需要初始化f[i][0] ,f[0][i] ,这样可以在i-1的时候避免边界检查。
3.1、120. Triangle
https://leetcode.com/problems/triangle/#/description
找到和最小的一条路径。
思路:
1)记忆化搜索memorize+ divide and conquer
class Solution {
public:
int helper(int x,int y,int size,vector<vector<int>>& triangle,vector<vector<int>>& minSum){
if(x >= size){
return ;
}
if(minSum[x][y] != INT_MAX){
return minSum[x][y];
} minSum[x][y] = min(helper(x + ,y, size,triangle,minSum),
helper(x + ,y + ,size,triangle,minSum))
+ triangle[x][y];
return minSum[x][y]; }
int minimumTotal(vector<vector<int>>& triangle) {
if(triangle.size() == || triangle[].size() == ){
return -;
}
int row = triangle.size();
int col = triangle[row - ].size();
vector<vector<int>> minSum(row,vector<int> (col,INT_MAX));
return helper(,,triangle.size(),triangle,minSum);
} };
memorize + divide and conquer
不管是二叉树还是其他这种树结构的,都是递归到叶子节点的下一个空节点再返回,这点可以节省很多思考过程,很好用。
if(x == size){
return ;
}
所谓记忆化搜索就是指用一个数组存储之前的状态,首先初始化数组全为INT_MAX,计算之前先看看是否计算过,计算过就直接返回。
if(minSum[x][y] != INT_MAX){
return minSum[x][y];
}
2)动态规划:
使用动态规划四步走方法,状态代表 state: f[x][y] = minimum path value from x,y to bottom,initialization:初始化最后一行,function:当前和等于当前值加上下一行的最小值,return:f[0][0]。
class Solution {
public:
//bottom to top
int minimumTotal(vector<vector<int>>& triangle) {
// state: f[x][y] = minimum path value from x,y to bottom
int col = triangle.size();
int row = triangle[col - ].size();
vector<vector<int>> f(col,vector<int> (row,));//f[i][j]代表从下到(i,j)位置的最小和
//initial
for(int i = ;i< row;++i){
f[col - ][i] = triangle[col - ][i];
}
//function,bottom to top
for(int i = col - ;i >= ;--i){
for(int j = ;j < row;++j){
f[i][j] = triangle[i][j] + min(f[i + ][j],f[i + ][j + ]);
}
}
//result
return f[][];
}
};
triangle dynamic programming
3.2 64. Minimum Path Sum
https://leetcode.com/problems/minimum-path-sum/tabs/description
思路:题目意思是从左上走到右下。和上面那题差不多。注意初始化第一行和第一列,然后function的时候从第二列和第二行开始,避免了 i-1 的边界检查。
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
if(grid.size() == || grid[].size() == ){
return -;
}
int m = grid.size();
int n = grid[].size();
//state
vector<vector<int>> sum(m,vector<int> (n,));
//intialization
sum[][] = grid[][];
for(int i = ;i < n;++i){
sum[][i] = sum[][i - ] + grid[][i];
}
for(int i = ;i < m;++i){
sum[i][] = sum[i - ][] + grid[i][];
}
//function
for(int i = ;i < m;++i){
for(int j = ;j < n;++j){
sum[i][j] = min(sum[i - ][j],sum[i][j - ]) + grid[i][j];
}
}
//result
return sum[m - ][n - ];
}
};
minimum path sum
3.3 62. Unique Paths
https://leetcode.com/problems/unique-paths/tabs/description
机器人从左上走到右下有多少条路径。
思路:按照动态规划四步走策略,num[i][j]代表从起点到(i,j)这点路径之和;矩阵型动态规划初始化第一行和第一列,都为1;function等于top和left的路径之和;
结果返回num[m - 1][n - 1].
class Solution {
public:
int uniquePaths(int m, int n) {
if(m == || n == ){
return -;
}
//state
vector<vector<int>> num(m,vector<int> (n,));
//initialation
num[][] = ;
for(int i =;i < n;++i){
num[][i] = ;
}
for(int j =;j < m;++j){
num[j][] = ;
}
//function
for(int i = ;i < m;++i){
for(int j = ;j < n;++j){
num[i][j] = num[i - ][j] + num[i][j - ];
}
}
//result
return num[m - ][n - ];
}
};
unique paths
3.4 63. Unique Paths II
https://leetcode.com/problems/unique-paths-ii/tabs/description/
机器人从左上走到右下有多少条路径。有障碍物的情况。
思路:这题和上题不同点在于有障碍物,对num[i][j]进行计算前要判断当前格子的值是否为1,为1的话就num赋值为0,不然就等于top和left的num值之和。
记住小细节:第一行和第一列进行赋值的时候,要注意先判断不是1,就将num赋值为1,不然直接break退出循环,因为vector定义的时候就已经初始化为0;
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
//exception
if(obstacleGrid.size() == || obstacleGrid[].size() == ){
return -;
}
//state
int m = obstacleGrid.size();
int n= obstacleGrid[].size();
vector<vector<int>> num(m,vector<int> (n,));
//initialation for(int i = ;i < n;++i){
if(obstacleGrid[][i] != ){
num[][i] = ;
}
else{
break;
}
}
for(int j = ;j < m;++j){
if(obstacleGrid[j][] != ){
num[j][] = ;
}
else{
break;
}
}
//function
for(int i = ;i < m;++i){
for(int j = ;j < n;++j){
if(obstacleGrid[i][j] == ){
num[i][j] = ;
}
else{
num[i][j] = num[i][j - ] + num[i - ][j];
}
}
}
//answer
return num[m - ][n - ];
}
};
Unique Paths II
4 序列型动态规划sequence dynamic programming
state: f[i]表示 “ 前 i” 个位置 /数字 /字母 ,(以第 i个 为 ).以第i个元素结尾的最值..
function: f[i] = f[j] … j 是 i之前的一个位置
intialize: f[0]..
answer: f[n-1]..
处理字符串类型的题目并且涉及到前i个字符,我们一般讲数组多开一个f(n + 1),数组大小是n + 1.
4.1 70. Climbing Stairs
https://leetcode.com/problems/climbing-stairs/tabs/description
思路:递归,循环,动态规划
大炮打小鸟:动态规划(这题属于count,计算方案个数,同时里面的数不能随意交换,是序列不是集合所以可以使用动态规划)
state: f[i]表示前 i个位置,跳到第 i个位置的方案
总 数
function: f[i] = f[i-1] + f[i-2]
intialize: f[0] = 1
answer: f[n]
class Solution {
public:
int climbStairs(int n) {
//state
vector<int> f(n + ,);
//intialization
f[] = ;
f[] = ;
//function
for(int i = ;i <= n;++i){
f[i] = f[i - ] + f[i - ];
}
//answer
return f[n];
}
};
climbStairs dynamic programming
循环:
class Solution {
public:
int climbStairs(int n) {
int a = ;
int b = ;
int c = ;
for(int i = ;i < n;++i){
c = a + b;
a = b;
b = c;
}
return c;
}
};
climbStairs iterator
4.2 55. Jump Game
https://leetcode.com/problems/jump-game/tabs/description
矩阵中的数字代表跳跃的步数,是否能够跳到最后一格。
思路:首先判断能否使用动态规划:1)属于YES/NO问题,2)元素是有序的不是集合 => 可以使用序列型动态规划
虽然动态规划超时,但是思路需要知道。
这题需要用到贪心算法,计算出整个序列所能达到的最大长度,只要最大长度大于数组的最后一个下标,就可以是true,
class Solution {
public:
bool canJump(vector<int>& nums) {
if(nums.empty()){
return false;
}
int longest = nums[] + ;
for(int i = ;i < nums.size();++i){
if(i <= longest && i + nums[i] > longest){
longest = i + nums[i];
}
}
return longest >= nums.size() - ;
}
};
55. Jump Game
4.3 45. Jump Game II
https://leetcode.com/problems/jump-game-ii/description/
数组中每个数字代表最大跳跃的步数,到达最后一个元素,最少需要多少步数。
思路:动态规划算法会超时,但是需要掌握。因为是最少跳跃步数,所以初始化为最大值INT_MAX;
state: f[i]代表我跳到这个位置最少需要几步
function: f[i] = MIN(f[j]+1, j < i && j能 够 跳到 i),每次操作就是上次跳的最小步数加1;
initialize: f[0] = 0;
answer: f[n-1]
class Solution {
public:
//最少跳跃步数
int jump(vector<int>& nums) {
if(nums.size() == ){
return ;
}
int n = nums.size();
vector<int> dp(n,INT_MAX);
dp[] = ;//初始化了第一个就不需要计算了
int minStep;
for(int i = ;i < n;++i){
minStep = INT_MAX;
for(int j = ;j < i;++j){
if(dp[j] != INT_MAX && nums[j] + j >= i && dp[j] + < minStep){
minStep = dp[j] + ; }
}
dp[i] = minStep;
}
return dp[n - ];
}
};
动态规划超时版本
//贪心算法我自己的理解版本
#一直对贪心不感冒#。。。。cur表示最远能覆盖到的地方。last表示已经覆盖的地方,ret表示步数,以[2,3,1,1,4]为例子;
1、初始化==>cur = 0;ret = 0;last = 0;
2、i= 0的时候==>cur = 2 + 0 = 2;ret = 0;last = 0;
3、i = 1的时候==>i > last,last需要更新为cur,即last = 2,同时cur = 1 + 3 = 4,因为last更新了一次,所以ret需要加1;
class Solution {
public:
int jump(vector<int>& nums) {
if(nums.size() == ){
return -;
}
int len = nums.size();
vector<int> small(len,INT_MAX);
//initalization
small[] = ;
//function
for(int i = ; i < len;++i){
for(int j = ;j < i;++j){
if(small[j] != INT_MAX && j + nums[j] >= i){
small[i] = min(small[i],small[j] + );
}
}
}
//answer
return small[len - ]; }
};
jump games II
贪心法:实验室小纸贴校外版参照
class Solution {
public:
int jump(vector<int>& nums) {
if(nums.size() == ){
return -;
}
int len = nums.size();
int cur = ;
int last = ;
int step = ;
for(int i = ;i < len;++i){
if(i > last){
last = cur;
++step;
}
cur = max(cur,i + nums[i]);
}
return step;
}
};
4.4 300. Longest Increasing Subsequence
https://leetcode.com/problems/longest-increasing-subsequence/description/
思路:f[i]代表num[i]结尾的最长序列,注意该题初始化的时候要全部初始化为1,不能只初始化f[0],因为[3,2,4]这种情况,f[1]没有初始化就为0,导致计算错误。
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if(nums.size() == ){
return ;
}
//state
int n = nums.size();
int maxNum = ;
vector<int> f(n,);//must all are equal 1
//intialization
// f[0] = 1;
//function
for(int i = ;i < n;++i){
for(int j = ;j < i;++j){
if(nums[j] < nums[i]){
f[i] = max(f[i],f[j] + );
maxNum = max(maxNum,f[i]);
}
}
}
//answer
return maxNum;
}
};
longest increasing subsequence
4.5 132. Palindrome Partitioning II
https://leetcode.com/problems/palindrome-partitioning-ii/description/
将一个字符串切割为回文串,求最小的切割次数。
思路:1)使用DFS
2)使用动态规划,传统的序列性动态规划,使用双指针方式判断回文的方法复杂度为n^3,超时了,所以使用两个动态规划,一个为区间型动态规划,序列性动态规划需要将数组多设一个,f(n + 1),初始化需要全部初始化,并且将第0个初始化为-1,
**简化回文判断复杂度的区间型动态规划:先判断两端字符是否相等,然后利用数组判断中间部分是否相等。,使用len长度控制。
/////////////////////////////////////////////////////////////////////////////////////*********//////////////////////////////////////////////////
state: f[i]”前i”个字符组成的子字符串需要最少几次cut(最少能被分割为多少个字符串-1)
function: f[i] = MIN{f[j]+1}, j < i && j+1 ~ i这一段是一个回文串
intialize: f[i] = i - 1 (f[0] = -1)
answer: f[s.length()]
/////////////////////////////////////////////////////////////////////////////////////*********//////////////////////////////////////////////////
class Solution {
public:
vector<vector<bool>> isPalindrome(string s){
vector<vector<bool>> result(s.size(),vector<bool> (s.size(),false));
for(int i = ;i < s.size();++i){
result[i][i] = true;
} for(int start = ;start < s.size() - ;++start){
result[start][start + ] = s[start] == s[start + ];
} for(int len = ; len < s.size();++len){
for(int start = ;start < s.size() - len;++start){
result[start][start + len] =
(result[start + ][start + len - ] && s[start] == s[start + len]); }
}
return result;
}
int minCut(string s) {
if(s.size() == ){
return -;
}
vector<vector<bool>> result = isPalindrome(s);
int n = s.size();
//state
vector<int> f(n + ,);//最少需要多少刀
//intialization
for(int i = ;i <= n;++i){
f[i] = i - ;
}
//function
for(int i = ;i <= n;++i){
for(int j = ;j < i;++j){
if(result[j][i - ]){
f[i] = min(f[j] + ,f[i]);
}
}
}
//answer
return f[n];
}
};
palindrome sequence
二刷版本:注意主程序需要考虑i == j。len从1开始,也可以考虑dp[n] ,不一定要dp[n + 1];
class Solution {
public: void judge(vector<vector<bool>> &res,string s){
for(int i = ;i < res.size();++i){
res[i][i] = true;
}
for(int i = ;i < res.size();++i){
res[i][i + ] = (s[i] == s[i + ]);
}
for(int len = ;len < res.size();++len){
for(int i = ;i < res.size() - len - ;++i){
res[i][i + len + ]= res[i + ][i + len] && (s[i] == s[i + len + ]);
}
}
} int minCut(string s) {
if(s.empty()){
return -;
}
int n = s.size();
vector<vector<bool>> res(n,vector<bool> (n,false));
vector<int> dp(n,); judge(res,s);
for(int i = ;i < n;++i){
dp[i] = i;
}
for(int i = ;i < n;++i){
for(int j = ;j <= i;++j){//这里一定要i==j结束
if(res[j][i]){
if(j == ){
dp[i] = ;
}
else{
dp[i] = min(dp[i],dp[j - ] + );
} }
}
}
return dp[n - ];
}
};
二刷
4.6 139. Word Break
https://leetcode.com/problems/word-break/description/
这道拆分词句问题是看给定的词句能分被拆分成字典里面的内容
思路:就是按照动态规划四步走战略,注意判断前提:首先是yes/no问题,然后是序列不是集合,所以是序列型动态规划。
1)预处理:使用unordered_set,将字典存储,这样以后查找才快;
2)state:vector<bool> f(n + 1,false);//前i个字符是否能分割为dictionary里面的单词;
3)intialization: f[0] = true;空字符肯定是存在的。
4)//function:前j个是字典中的并且j和i中间的单词也是字典中的,那么前i个就是TRUE,
if(f[j] == true && setWordDict.find(subString) != setWordDict.end()){
f[i] = true;
}
5)answer:f[n]。
class Solution {
public:
set<string> WordDict(vector<string>& wordDict){
set<string> result;
for(string str : wordDict){
result.insert(str);
}
return result;
}
bool wordBreak(string s, vector<string>& wordDict) {
if(s.size() == ){
return true;
}
if(wordDict.size() == ){
return false;
}
//preparation
set<string> setWordDict = WordDict(wordDict);
//state
int n = s.size();
vector<bool> f(n + ,false);//前i个字符是否能分割为dictionary里面的单词
//intialization
f[] = true;
//function
for(int i = ;i <= n;++i){
for(int j = ;j < i;++j){
string subString = s.substr(j,i - j);//(i - 1) -j + 1
if(f[j] == true && setWordDict.find(subString) != setWordDict.end()){
f[i] = true;
}
}
}
//answer
return f[n];
}
};
Word break
二刷错误点
1)goals,,字典是go,goal,goals
所以不能一找到第一个go就break。
2)
int ix = ;
for(ix = ;ix < n;++ix){
if(wordSet.find(s.substr(,ix + )) != wordSet.end()){
dp[ix] = true;
//break;
}
}
// if(ix == n){
// return dp[ix - 1];
// }开始这里没注释,直接每次退出循环ix都等于n,总是出错,因为是原来break掉,才有这句
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
if(s.size() == ){
return false;
}
if(wordDict.size() == ){
return ;
}
int n = s.size();
vector<bool> dp(n,false);
//hashset
unordered_set<string> wordSet;
for(int i = ;i < wordDict.size();++i){
wordSet.insert(wordDict[i]);
}
//find first true
int ix = ;
for(ix = ;ix < n;++ix){
if(wordSet.find(s.substr(,ix + )) != wordSet.end()){
dp[ix] = true;
//break;
}
}
// if(ix == n){
// return dp[ix - 1];
// }开始这里没注释,直接每次退出循环ix都等于n,总是出错,因为是原来break掉,才有这句
//funciton
for(int i = ;i < n;++i){
for(int j = ;j <= i;++j){
if((dp[j - ] == true) && (wordSet.find(s.substr(j,i - j + )) != wordSet.end())){
dp[i] = true;
}
}
} return dp[n - ];
}
};
二刷这题不难就是传统序列动态规划
那道题只让我们判断给定的字符串能否被拆分成字典中的词,而这道题加大了难度,让我们求出所有可以拆分成的情况,
5、双序列型动态规划。
一定要注意:不管是双序列还是单序列动态规划,都必须注意求前n个这种情况。比如:seq和f[i],f数组中f[i],对应到seq中的计算必须是seq[i - 1]。
5.1 Longest Common Subsequence
最长公共子序列
http://www.lintcode.com/en/problem/longest-common-subsequence/
思路:1)f[i][j]代表a前i个字符和b前j个字符的相等的长度。
这题最容易错的地方就是:seq和f[i],f数组中f[i],对应到seq中的计算必须是seq[i - 1],毕竟在状态定义中,f数组指的是前i个字符。
2)intialization:初始化为0;
3)function:就是递推方程;
4)answer:f[n][m]。
class Solution {
public:
/**
* @param A, B: Two strings.
* @return: The length of longest common subsequence of A and B.
*/
int longestCommonSubsequence(string A, string B) {
// write your code here
if(A.size() == || B.size() == ){
return ;
}
int n = A.size();
int m = B.size();
//state
vector<vector<int>> f(n + ,vector<int> (m + ,));
//intialization //function
for(int i = ;i <= n;++i){
for(int j = ;j <= m;++j){
if(A[i - ] == B[j - ]){
f[i][j ] = f[i - ][j - ] + ;
}
else{
f[i][j] = max(f[i][j - ],f[i - ][j]);
}
}
}
//answer
return f[n][m];
}
};
longest common substring
5.2 72. Edit Distance
https://leetcode.com/problems/edit-distance/description/
将字符串1变为字符串2,最小的编辑距离。
这里注意一个错误信息:min函数里面比较的只能是两个数,如果比较三个数就会出错“required from here ”。
思路:1)state:f[i][j]a中的前i 个字符经过多少次编辑变为b中的前j个字符。
2)function:f[i][j] = MIN( f[i-1][j]+1插入, f[i][j-1]+1删除, f[i-1][j-1]不变 ) // a[i] == b[j]
= MIN( f[i-1][j]+1插入, f[i][j-1]+1删除, f[i-1][j-1]+1替换 ) // a[i] != b[j]
(帮助记忆:假设i等于j,i-1就是需要增加一个才能等于j,j - 1就需要i删除一个才能使得两者相等)
3)intialize:f[i][0] = i, f[0][j] = j ;
4)answer: f[a.length()][b.length()]
class Solution {
public:
int minDistance(string word1, string word2) {
int n = word1.size(),m = word2.size();
if(n == && m == ){
return ;
}
//state
vector<vector<int>> f(n + ,vector<int> (m + ,));
//intialization
for(int i = ;i <= n;++i){
f[i][] = i;
}
for(int j = ;j <= m;++j){
f[][j] = j;
}
//function
for(int i = ;i <= n;++i){
for(int j = ;j <= m;++j){
if(word1[i - ] == word2[j - ]){
f[i][j] = min(f[i - ][j - ],min(f[i][j - ] + ,f[i - ][j] + ));
}
else{ f[i][j] = min(f[i - ][j - ] + ,min(f[i][j - ] + ,f[i - ][j] + ));
}
}
}
//answer
return f[n][m];
}
};
edit distance
5.3 115. Distinct Subsequences
https://leetcode.com/problems/distinct-subsequences/description/
思路:题目意思是source字符串中找出和target字符串相等的子串有多少个。记住那两个for循环,一个是i <=n,j<= m,只要是双序列动态规划都是这样。
初始化的时候,要注意初始化第一个f[0[0],初始化第一个列和第一行会重复第1个,所以要注意这里。初始化技巧先全部初始化为0,后面就看哪些情况不是0的就特殊处理就好了。
//intialize
for(int i = ;i <= n;++i){
f[i][] = ;
cout << f[i][];
}
for(int j = ;j <= m;++j){//初始化第一个f[0][0]出现冲突,导致出错
f[][j] = ;
}
state: f[i][j] 表示 S的前i个字符中选取T的前j个字符,有多少种方案
function:第j个字符是不能去掉的,都是分为相等和不相等这两种情况进行处理,
如果i和j相等,就看前i-1个字符和前j个字符的情况以及前i-1和j-1个字符判断,
不相等的话就看前i-1个字符和前j个字符的情况。
f[i][j] = f[i - 1][j] + f[i - 1][j - 1] (S[i-1] == T[j-1])
= f[i - 1][j] (S[i-1] != T[j-1])
initialize: f[i][0] = 1, f[0][j] = 0 (j > 0)
answer: f[n][m] (n = sizeof(S), m = sizeof(T))
class Solution {
public:
int numDistinct(string s, string t) {
int n = s.size();
int m = t.size();
if(n == && m == ){
return ;
}
//state
vector<vector<int>> f(n + ,vector<int> (m + ,));
//intialize
for(int i = ;i <= n;++i){
f[i][] = ;
cout << f[i][];
}
for(int j = ;j <= m;++j){//初始化第一个f[0][0]出现冲突,导致出错
f[][j] = ;
}
//function for(int i = ;i <= n;++i){
for(int j = ;j <= m;++j){
if(s[i - ] == t[j - ]){
f[i][j] = f[i - ][j] + f[i - ][j - ]; }
else{
f[i][j] = f[i - ][j];
}
}
}
//answer
return f[n][m];
}
};
distinct subsequence
5.4 97. Interleaving String
s3是否是由s1和s2交错形成的字符串。
思路:先填满一个dp数组,然后想第一行第一列,然后找规律得递推关系。参考交织相错的字符串。只要是遇到字符串的子序列或是匹配问题直接就上动态规划Dynamic Programming,其他的都不要考虑。
技巧:初始化的时候先单独初始化第一个,然后再循环初始化第一行和第一列。
state: f[i][j]表示s1的前i个字符和s2的前j个字符能否交替组成s3的前i+j个字符
前i个和前j个组成前i + j个,对应的下标是i + j - 1
function: f[i][j] = (f[i-1][j] && (s1[i-1]==s3[i+j-1]) || f[i][j-1] && (s2[j-1]==s3[i+j-1])----注意f数组是前i个,对应s数组下标是i-1.
initialize: f[i][0] = s1[0..i-1] = s3[0..i-1]
f[0][j] = s2[0..j-1] = s3[0..j-1]
answer: f[n][m] n = sizeof(s1), m = sizeof(s2)
class Solution {
public:
bool isInterleave(string s1, string s2, string s3) {
if(s1.size() + s2.size() != s3.size()){
return false;
}
int n = s1.size(),m = s2.size();
//state
vector<vector<bool>> dp(n + ,vector<bool> (m + ,false));
//intialize
dp[][] = true;
for(int i = ;i <= n;++i){
if(s1[i - ] == s3[i - ] && dp[i - ][] == true){
dp[i][] = true;
}
}
for(int j = ;j <= m;++j){
if(s2[j - ] == s3[j - ] && dp[][j - ] == true){
dp[][j] = true;
}
}
//function
for(int i = ;i <= n;++i){
for(int j = ;j <= m;++j){
if(s1[i - ] == s3[i - + j] && dp[i - ][j] || s2[j - ] == s3[i - + j] && dp[i][j - ]){
dp[i][j] = true;
}
}
}
return dp[n][m];
}
};
interleaving string交错字符串
6专题总结-动态规划dynamic programming的更多相关文章
- 动态规划Dynamic Programming
动态规划Dynamic Programming code教你做人:DP其实不算是一种算法,而是一种思想/思路,分阶段决策的思路 理解动态规划: 递归与动态规划的联系与区别 -> 记忆化搜索 -& ...
- 动态规划(Dynamic Programming)算法与LC实例的理解
动态规划(Dynamic Programming)算法与LC实例的理解 希望通过写下来自己学习历程的方式帮助自己加深对知识的理解,也帮助其他人更好地学习,少走弯路.也欢迎大家来给我的Github的Le ...
- 动态规划 Dynamic Programming
March 26, 2013 作者:Hawstein 出处:http://hawstein.com/posts/dp-novice-to-advanced.html 声明:本文采用以下协议进行授权: ...
- [算法]动态规划(Dynamic programming)
转载请注明原创:http://www.cnblogs.com/StartoverX/p/4603173.html Dynamic Programming的Programming指的不是程序而是一种表格 ...
- 最优化问题 Optimization Problems & 动态规划 Dynamic Programming
2018-01-12 22:50:06 一.优化问题 优化问题用数学的角度来分析就是去求一个函数或者说方程的极大值或者极小值,通常这种优化问题是有约束条件的,所以也被称为约束优化问题. 约束优化问题( ...
- 动态规划系列(零)—— 动态规划(Dynamic Programming)总结
动态规划三要素:重叠⼦问题.最优⼦结构.状态转移⽅程. 动态规划的三个需要明确的点就是「状态」「选择」和「base case」,对应着回溯算法中走过的「路径」,当前的「选择列表」和「结束条件」. 某种 ...
- 动态规划 Dynamic Programming 学习笔记
文章以 CC-BY-SA 方式共享,此说明高于本站内其他说明. 本文尚未完工,但内容足够丰富,故提前发布. 内容包含大量 \(\LaTeX\) 公式,渲染可能需要一些时间,请耐心等待渲染(约 5s). ...
- Python算法之动态规划(Dynamic Programming)解析:二维矩阵中的醉汉(魔改版leetcode出界的路径数)
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_168 现在很多互联网企业学聪明了,知道应聘者有目的性的刷Leetcode原题,用来应付算法题面试,所以开始对这些题进行" ...
- 后台开发 3个题目 array_chunk, 100块钱找零钱(动态规划 dynamic programming), 双向循环链表 llist 删除节点
1. array_chunk 实现 http://php.net/manual/en/function.array-chunk.php <?php function my_array_chunk ...
随机推荐
- PyQt5的菜单栏、工具栏和状态栏
1.创建菜单栏import sys, mathfrom PyQt5.QtWidgets import *from PyQt5.QtGui import *from PyQt5.QtCore impor ...
- docker运行安装mysql postgres
安装mysql [root@host1 ~]# docker images -a REPOSITORY TAG IMAGE ID CREATED SIZE docker.io/mysql 5.7 4d ...
- 关于cmd的命令行参数的问题
最近学习Java了解到发现需要配置环境变量其中Path需要更改为 %JAVA_HOME%\bin;%JAVA_HOME%\jre\bin; 而这样的行为无意间导致了win中cmd的一些参数无法使用,比 ...
- linux压缩包管理
1.gzip 文件 ----> .gz格式的压缩包 2.bzip2 文件 ----> .bz2格式的压缩包 3.tar -- 不使用z/j参数 该命令只能对文件或目录打包 参数: c -- ...
- BUUCTF知识记录
[强网杯 2019]随便注 先尝试普通的注入 发现注入成功了,接下来走流程的时候碰到了问题 发现过滤了select和where这个两个最重要的查询语句,不过其他的过滤很奇怪,为什么要过滤update, ...
- F: Horse Pro 马走棋盘 BFS
F: Horse Pro 豆豆也已经开始学着玩象棋了,现在豆豆已经搞清楚马的走法了,但是豆豆不能确定能否在 100 步以内从一个点到达另一个点(假设棋盘无限大). Input 第一行输入两个整数 x1 ...
- LeetCode 445. Add Two Numbers II(链表求和)
题意:两个非空链表求和,这两个链表所表示的数字没有前导零,要求不能修改原链表,如反转链表. 分析:用stack分别存两个链表的数字,然后从低位开始边求和边重新构造链表. Input: (7 -> ...
- 8个问题全面了解5G关键技术Massive MIMO
1 什么是Massive MIMO Massive MIMO(大规模天线技术,亦称为Large Scale MIMO)是第五代移动通信(5G)中提高系统容量和频谱利用率的关键技术.它最早由美国贝尔实验 ...
- mysql 统计索引执行情况
select distinct b.TABLE_SCHEMA,b.TABLE_NAME , b.INDEX_NAME , a.count_starfrom performance_schema.tab ...
- 快速幂 & 矩阵快速幂
目录 快速幂 实数快速幂 矩阵快速幂 快速幂 实数快速幂 普通求幂的方法为 O(n) .在一些要求比较严格的题目上很有可能会超时.所以下面来介绍一下快速幂. 快速幂的思想其实是将数分解,即a^b可以分 ...