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 0;
        }
        if(minSum[x][y] != INT_MAX){
            return minSum[x][y];
        }
        
        minSum[x][y] = min(helper(x + 1,y,    size,triangle,minSum),
                           helper(x + 1,y + 1,size,triangle,minSum))
                           + triangle[x][y];
        return minSum[x][y];
        
    }
    int minimumTotal(vector<vector<int>>& triangle) {
        if(triangle.size() == 0 || triangle[0].size() == 0){
            return -1;
        }
        int row = triangle.size();
        int col = triangle[row - 1].size();
        vector<vector<int>> minSum(row,vector<int> (col,INT_MAX));       
        return helper(0,0,triangle.size(),triangle,minSum);
    }

    
};
memorize + divide and conquer

不管是二叉树还是其他这种树结构的,都是递归到叶子节点的下一个空节点再返回,这点可以节省很多思考过程,很好用。

 if(x == size){
    return 0;
 }

所谓记忆化搜索就是指用一个数组存储之前的状态,首先初始化数组全为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 - 1].size();
        vector<vector<int>> f(col,vector<int> (row,0));//f[i][j]代表从下到(i,j)位置的最小和
        //initial
        for(int i = 0;i< row;++i){
            f[col - 1][i] = triangle[col - 1][i];
        }
        //function,bottom to top 
        for(int i = col - 2;i >= 0;--i){
            for(int j = 0;j < row;++j){
                f[i][j] = triangle[i][j] + min(f[i + 1][j],f[i + 1][j + 1]);
            }
        }
        //result
        return f[0][0];
    }
};
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() == 0 || grid[0].size() == 0){
            return -1;
        }
        int m = grid.size();
        int n = grid[0].size();
        //state
        vector<vector<int>> sum(m,vector<int> (n,0));
        //intialization
        sum[0][0] = grid[0][0];
        for(int i = 1;i < n;++i){
            sum[0][i] = sum[0][i - 1] + grid[0][i];
        }
        for(int i = 1;i < m;++i){
            sum[i][0] = sum[i - 1][0] + grid[i][0];
        }
        //function
        for(int i = 1;i < m;++i){
            for(int j = 1;j < n;++j){
                sum[i][j] = min(sum[i - 1][j],sum[i][j - 1]) + grid[i][j];
            }
        }
        //result
        return sum[m - 1][n - 1];
    }
};
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 == 0 || n == 0){
            return -1;
        }
        //state
        vector<vector<int>> num(m,vector<int> (n,0));
        //initialation
        num[0][0] = 1;
        for(int i =0;i < n;++i){
            num[0][i] = 1;
        }
        for(int j =0;j < m;++j){
            num[j][0] = 1;
        }
        //function
        for(int i = 1;i < m;++i){
            for(int j = 1;j < n;++j){
                num[i][j] = num[i - 1][j] + num[i][j - 1];
            }
        }
        //result
        return num[m - 1][n - 1];
    }
};
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() == 0 || obstacleGrid[0].size() == 0){
            return -1;
        }
        //state
        int m = obstacleGrid.size();
        int n= obstacleGrid[0].size();
        vector<vector<int>> num(m,vector<int> (n,0));
        //initialation
       
        for(int i = 0;i < n;++i){
            if(obstacleGrid[0][i] != 1){
                 num[0][i] = 1;
            }
            else{
                break;
            }           
        }
         for(int j = 0;j < m;++j){
            if(obstacleGrid[j][0] != 1){
                 num[j][0] = 1;
            }
            else{
                break;
            }           
        }
        //function
        for(int i = 1;i < m;++i){
            for(int j = 1;j < n;++j){
                if(obstacleGrid[i][j] == 1){
                    num[i][j] = 0;
                }
                else{
                    num[i][j] = num[i][j - 1] + num[i - 1][j];
                }
            }
        }
        //answer
        return num[m - 1][n - 1];
    }
};
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 + 1,0);
        //intialization
        f[0] = 1;
        f[1] = 1;
        //function
        for(int i = 2;i <= n;++i){
            f[i] = f[i - 1] + f[i - 2];
        }
        //answer
        return f[n];
    }
};
climbStairs dynamic programming

循环:

class Solution {
public:
    int climbStairs(int n) {
        int a = 1;
        int b = 1;
        int c = 1;        
        for(int i = 1;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[0] + 0;
       for(int i = 1;i < nums.size();++i){
           if(i <= longest && i + nums[i] > longest){
               longest = i + nums[i];
           }
       }
        return longest >= nums.size() - 1;
    }
};
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() == 0){
            return 0;
        }
        int n = nums.size();
        vector<int> dp(n,INT_MAX);
        dp[0] = 0;//初始化了第一个就不需要计算了
        int minStep;
        for(int i = 1;i < n;++i){
            minStep = INT_MAX;
            for(int j = 0;j < i;++j){
                if(dp[j] != INT_MAX && nums[j] + j >= i && dp[j] + 1 < minStep){
                    minStep = dp[j] + 1;
                    
                }                
            }            
            dp[i] = minStep;
        }
        return dp[n - 1];
    }
};
动态规划超时版本

 //贪心算法我自己的理解版本

#一直对贪心不感冒#。。。。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() == 0){
            return -1;
        }
        int len = nums.size();
        vector<int> small(len,INT_MAX);
        //initalization
        small[0] = 0;
        //function
        for(int i = 1; i < len;++i){
            for(int j = 0;j < i;++j){
                if(small[j] != INT_MAX && j + nums[j] >= i){
                      small[i] = min(small[i],small[j] + 1);
                }
            }
        }
        //answer
        return small[len - 1];
        
    } 
};
jump games II

贪心法:实验室小纸贴校外版参照

class Solution {
public:
    int jump(vector<int>& nums) {
        if(nums.size() == 0){
            return -1;
        }
        int len = nums.size();
        int cur = 0;
        int last = 0;
        int step = 0;
        for(int i = 0;i < len;++i){
            if(i > last){
                last = cur;
                ++step;
            }
            cur = max(cur,i + nums[i]);
        }
        return step;
    } 
};
View Code

 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() == 0){
            return 0;
        }
        //state
        int n = nums.size();
        int maxNum = 1;
        vector<int> f(n,1);//must all are equal 1
        //intialization
        // f[0] = 1;
        //function
        for(int i = 1;i < n;++i){
            for(int j = 0;j < i;++j){
                if(nums[j] < nums[i]){
                    f[i] = max(f[i],f[j] + 1);
                    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 = 0;i < s.size();++i){
            result[i][i] = true;
        }
        
        for(int start = 0;start < s.size() - 1;++start){
            result[start][start + 1] =  s[start] == s[start + 1];
        }
        
        
        for(int len = 2; len < s.size();++len){
            for(int start = 0;start < s.size() - len;++start){
                result[start][start + len] = 
                    (result[start + 1][start + len - 1] && s[start] == s[start + len]);          
               
            }
        }
        return result;
    }
    int minCut(string s) {
        if(s.size() == 0){
          return -1;
        }
        vector<vector<bool>> result = isPalindrome(s);
        int n = s.size();
        //state
        vector<int> f(n + 1,0);//最少需要多少刀
        //intialization        
        for(int i = 0;i <= n;++i){
            f[i] = i - 1;
        }
        //function        
        for(int i = 1;i <= n;++i){
            for(int j = 0;j < i;++j){
                if(result[j][i - 1]){
                    f[i] = min(f[j] + 1,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 = 0;i < res.size();++i){
            res[i][i] = true;
        }
        for(int i = 0;i < res.size();++i){
            res[i][i + 1] = (s[i]  == s[i + 1]);
        }
        for(int  len = 1;len < res.size();++len){
            for(int  i = 0;i < res.size() - len - 1;++i){
                res[i][i + len +  1]= res[i + 1][i + len] && (s[i] == s[i + len + 1]);
            }
        }
    }
    
    int minCut(string s) {
        if(s.empty()){
            return -1;
        }
        int n = s.size();
        vector<vector<bool>> res(n,vector<bool> (n,false));
        vector<int> dp(n,0);
        
        judge(res,s);
        for(int i = 0;i < n;++i){
            dp[i] = i;
        }
        for(int i = 0;i < n;++i){
            for(int j = 0;j <= i;++j){//这里一定要i==j结束
                if(res[j][i]){
                    if(j == 0){
                        dp[i] = 0;
                    }
                    else{
                       dp[i] =  min(dp[i],dp[j - 1] + 1); 
                    }
                    
                }               
            }
        }
        return dp[n - 1];
    }
};
二刷

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() == 0){
            return true;
        }
        if(wordDict.size() == 0){
            return false;
        }
        //preparation
        set<string> setWordDict = WordDict(wordDict);
        //state
        int n = s.size();
        vector<bool> f(n + 1,false);//前i个字符是否能分割为dictionary里面的单词
        //intialization
        f[0] = true;
        //function
        for(int i = 1;i <= n;++i){
            for(int j = 0;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 = 0;
        for(ix = 0;ix < n;++ix){            
            if(wordSet.find(s.substr(0,ix + 1)) != 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() == 0){
            return false;
        }
        if(wordDict.size() == 0){
            return 0;
        }
        int n = s.size();
        vector<bool> dp(n,false);
        //hashset
        unordered_set<string> wordSet;
        for(int i = 0;i < wordDict.size();++i){
            wordSet.insert(wordDict[i]);
        }
        //find first true
        int ix = 0;
        for(ix = 0;ix < n;++ix){            
            if(wordSet.find(s.substr(0,ix + 1)) != wordSet.end()){
                dp[ix] = true;
                //break;             
            }
        }
        // if(ix == n){
        //     return dp[ix - 1];
        // }开始这里没注释,直接每次退出循环ix都等于n,总是出错,因为是原来break掉,才有这句
        //funciton
        for(int i = 0;i < n;++i){
            for(int j = 1;j <= i;++j){                 
                if((dp[j - 1] == true) && (wordSet.find(s.substr(j,i - j + 1)) != wordSet.end())){
                    dp[i] = true;                   
                }
            }
        }
        
        return dp[n - 1];
    }
};
二刷这题不难就是传统序列动态规划

 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() == 0 || B.size() == 0){
            return 0;
        }
        int n = A.size();
        int m = B.size();
        //state
        vector<vector<int>> f(n + 1,vector<int> (m + 1,0));
        //intialization
        
        //function
        for(int i = 1;i <= n;++i){
            for(int j = 1;j <= m;++j){
                if(A[i - 1] == B[j - 1]){
                    f[i][j ]  = f[i - 1][j - 1] + 1;
                }
                else{
                    f[i][j]  = max(f[i][j - 1],f[i - 1][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 == 0 && m == 0){
            return 0;
        }
        //state
        vector<vector<int>> f(n + 1,vector<int> (m + 1,0));
        //intialization
        for(int i = 0;i <= n;++i){
            f[i][0] = i;
        }
        for(int j = 0;j <= m;++j){
               f[0][j] = j; 
        }
        //function
        for(int i = 1;i <= n;++i){
            for(int j = 1;j <= m;++j){
                 if(word1[i - 1] == word2[j - 1]){
                     f[i][j] = min(f[i - 1][j - 1],min(f[i][j - 1] + 1,f[i - 1][j] + 1));
                 }
                else{
                    
                    f[i][j] = min(f[i - 1][j - 1] + 1,min(f[i][j - 1] + 1,f[i - 1][j] + 1));
                }
            }
        }
        //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 = 0;i <= n;++i){
            f[i][0] = 1;
            cout << f[i][0];
        }
        for(int j = 1;j <= m;++j){//初始化第一个f[0][0]出现冲突,导致出错
            f[0][j] = 0;
        }

 

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 == 0 && m == 0){
            return 1;
        }
        //state
        vector<vector<int>> f(n + 1,vector<int> (m + 1,0));
        //intialize
        for(int i = 0;i <= n;++i){
            f[i][0] = 1;
            cout << f[i][0];
        }
        for(int j = 1;j <= m;++j){//初始化第一个f[0][0]出现冲突,导致出错
            f[0][j] = 0;
        }
        //function
         
        for(int i = 1;i <= n;++i){
            for(int j = 1;j <= m;++j){
                if(s[i - 1] == t[j - 1]){
                    f[i][j] = f[i - 1][j] + f[i - 1][j - 1];
                   
                }
                else{
                    f[i][j] = f[i - 1][j];
                }
            }
        }
        //answer
        return f[n][m];
    }
};
distinct subsequence

 5.4  97. Interleaving String

s3是否是由s1和s2交错形成的字符串。

思路:先填满一个dp数组,然后想第一行第一列,然后找规律得递推关系。参考交织相错的字符串。只要是遇到字符串的子序列或是匹配问题直接就上动态规划Dynamic Programming,其他的都不要考虑。

技巧:初始化的时候先单独初始化第一个,然后再循环初始化第一行和第一列。

state: f[i][j]表示s1i个字符和s2j个字符能否交替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 + 1,vector<bool> (m + 1,false));
        //intialize
        dp[0][0] = true;
        for(int i = 1;i <= n;++i){
            if(s1[i - 1] == s3[i - 1] && dp[i - 1][0] == true){
                dp[i][0] = true;
            }
        }
        for(int j = 1;j <= m;++j){
            if(s2[j - 1] == s3[j - 1] && dp[0][j - 1] == true){
                dp[0][j] = true;
            }
        }
        //function
        for(int i = 1;i <= n;++i){
            for(int j = 1;j <= m;++j){
                if(s1[i - 1] == s3[i - 1 + j] && dp[i - 1][j] || s2[j - 1] == s3[i - 1 + j] && dp[i][j - 1]){
                    dp[i][j] = true;
                }
            }
        }
        return dp[n][m];
    }
};
interleaving string交错字符串

 

posted @ 2017-07-23 12:14  zqlucky  阅读(491)  评论(0编辑  收藏  举报