专题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 - ];
}
};

二刷这题不难就是传统序列动态规划

word break II以及总结

那道题只让我们判断给定的字符串能否被拆分成字典中的词,而这道题加大了难度,让我们求出所有可以拆分成的情况,

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的更多相关文章

  1. 动态规划Dynamic Programming

    动态规划Dynamic Programming code教你做人:DP其实不算是一种算法,而是一种思想/思路,分阶段决策的思路 理解动态规划: 递归与动态规划的联系与区别 -> 记忆化搜索 -& ...

  2. 动态规划(Dynamic Programming)算法与LC实例的理解

    动态规划(Dynamic Programming)算法与LC实例的理解 希望通过写下来自己学习历程的方式帮助自己加深对知识的理解,也帮助其他人更好地学习,少走弯路.也欢迎大家来给我的Github的Le ...

  3. 动态规划 Dynamic Programming

    March 26, 2013 作者:Hawstein 出处:http://hawstein.com/posts/dp-novice-to-advanced.html 声明:本文采用以下协议进行授权: ...

  4. [算法]动态规划(Dynamic programming)

    转载请注明原创:http://www.cnblogs.com/StartoverX/p/4603173.html Dynamic Programming的Programming指的不是程序而是一种表格 ...

  5. 最优化问题 Optimization Problems & 动态规划 Dynamic Programming

    2018-01-12 22:50:06 一.优化问题 优化问题用数学的角度来分析就是去求一个函数或者说方程的极大值或者极小值,通常这种优化问题是有约束条件的,所以也被称为约束优化问题. 约束优化问题( ...

  6. 动态规划系列(零)—— 动态规划(Dynamic Programming)总结

    动态规划三要素:重叠⼦问题.最优⼦结构.状态转移⽅程. 动态规划的三个需要明确的点就是「状态」「选择」和「base case」,对应着回溯算法中走过的「路径」,当前的「选择列表」和「结束条件」. 某种 ...

  7. 动态规划 Dynamic Programming 学习笔记

    文章以 CC-BY-SA 方式共享,此说明高于本站内其他说明. 本文尚未完工,但内容足够丰富,故提前发布. 内容包含大量 \(\LaTeX\) 公式,渲染可能需要一些时间,请耐心等待渲染(约 5s). ...

  8. Python算法之动态规划(Dynamic Programming)解析:二维矩阵中的醉汉(魔改版leetcode出界的路径数)

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_168 现在很多互联网企业学聪明了,知道应聘者有目的性的刷Leetcode原题,用来应付算法题面试,所以开始对这些题进行" ...

  9. 后台开发 3个题目 array_chunk, 100块钱找零钱(动态规划 dynamic programming), 双向循环链表 llist 删除节点

    1. array_chunk 实现 http://php.net/manual/en/function.array-chunk.php <?php function my_array_chunk ...

随机推荐

  1. 绕过QQ群文件下载限速

    绕过QQ群文件下载限速 引言 众所周知,用QQ客户端下载QQ群文件,速度往往被限为10KB/s.这里我们来讲讲如何绕过这一限制. 原始事件发生在2020年2月2日,值武汉疫情爆发,全国各省市纷纷下令推 ...

  2. P3376 【模板】网络最大流 dinic详解

    dinic的核心在于分层和多路增广. 分层的意思是,对于图用bfs搜出每一层,避免出现dfs深度过深的情况. 多路增广,利用的是dfs的回溯性质,这样就可以在一个点增广出它的所有流量. #includ ...

  3. java实现文字转语音功能(仅Windows)

    一.pom.xml引入jar包依赖 <!-- https://mvnrepository.com/artifact/com.jacob/jacob 文字转语音 --> <depend ...

  4. ASA设置某些log不发送到log server

    If you want to suppress a specific syslog message to be sent to syslog server, then you must enter t ...

  5. Linux--如何通过图形界面选项快速更改ubuntu的窗口、图标、分辨率大小,超详细超实用~(适合所有人群)

    这是默认ubuntu给出的分辨率大小:800*600,说实话有点小不适合操作. (分辨率800*600) 这是调整后的分辨率大小. (分辨率1400*900) 操作方法: 首先点击选项框中的设置图标- ...

  6. uniGUI学习汇总

    UniGUI之UniLabel(31) uniGUI之自定义JS事件动作ClientEvents(30) uniGUI之文件下载(29) uniGUI之FDQuery(28) uniGUI之UniPo ...

  7. Weka算法算法翻译(部分)

    目录 Weka算法翻译(部分) 1. 属性选择算法(select attributes) 1.1 属性评估方法 1.2 搜索方法 2. 分类算法 2.1 贝叶斯算法 2.2 Functions 2.3 ...

  8. 1.项目创建及集成git

    1.创建项目 (1)创建完以后,打开Terminal命令行,输入"git init"用于初始化本地仓库 (2)打开对应的项目文件夹,“Ctrl+h”可以查看该文件夹里是否存在.gi ...

  9. vue注册全局过滤器

    1.src目录下创建filter文件 /** * 男女 * @param val * @returns {string} */ const status = val => { let name ...

  10. ssh访问ubuntu13.10

    步骤: 首先确保网络连接是ok,网络连接方式"桥接“,手动配置 ip 192.168.1.9,和主机是同一网段 1.检查当前有没有安装openssh-server(已安装) 2. 安装ope ...