Bootstrap

子串与子序列问题

前言

首先子串!=子序列

  • 子串:需要连续,如{abdgfr}的子串有abd,dgfr等
  • 子序列:不需要连续,只需要保持元素间相对有序。如 {abdgfr}的子序列有adf,bgf等

子串等于连续子序列

我们常见的子串或子序列问题有最大子串,最长递增子序列,最长公共子串,最长公共子序列问题等。这些问题均可以用动态规划来解答,解答分为3步:

  • 定义状态
  • 找到状态转移方程
  • 从所有状态中筛选答案

而子串与子序列问题状态定义均类似,用dp[i]定义为以i元素结尾的状态。实际上只要定义好状态,状态转移方程就很明显了。下面分别来说明:

最大子串问题

最大子串问题指的是数组中和最大的连续子序列。
题目参考:leedcode-53

  • 定义状态:dp[i]表示以第i个元素结尾的最大子串
  • 状态转移:dp[i]=max(dp[i-1]+num[i-1], num[i-1])
  • 数组的最大子串和为max(dp[i])
int maxSubArray(vector<int>& nums) {
    int res=INT_MIN;
    vector<int> dp(nums.size()+1,0);
    for(int i=1;i<=nums.size();i++)
    {
        dp[i]=max(dp[i-1]+nums[i-1],nums[i-1]);
        res=max(res,dp[i]);
    }
    return res;
}

由于每次状态转移只用到了上一次的状态,所以其实只需要用一个变量curMax来保存每次的状态。

int maxSubArray(vector<int>& nums) {
    int res=INT_MIN;
    int curMax=0;
    for(int i=0;i<nums.size();i++)
    {
        curMax=max(curMax+nums[i],nums[i]);
        res=max(res,curMax);
    }
    return res;
}

最长递增子序列问题

最长递增子序列又称LIS(Longest Increasing Subsequence),顾名思义为求最长的递增的子序列。
题目参考:leedcode-300

  • 定义状态:dp[i]表示num[i]结尾的最长递增子序列的长度
  • 状态转移:
    • dp[i]=max(dp[j]+1) { j<i && num[i]>num[j] }
    • or dp[i]=1(此时它比前面元素都小,因此只有它本身)
  • 最长递增序列长度为max(dp[i])
    int lengthOfLIS(vector<int>& nums) {
        int res = 0;
        int n = nums.size();
        vector<int>dp(n, 1);
        for(int i = 0 ;i<n;i++){
            for(int j=0;j<i;j++){
                if(nums[i]>nums[j]){
                    dp[i] = max(dp[j]+1, dp[i]);
                }
            }
            res = max(dp[i], res);
        }
        return res;
    }

最长公共子串问题

求两个数组S1,S2的最长的公共子串。
题目参考:leedcode-718

  • 定义状态:dp[i][j]表示以S1第i个元素结尾且以S2第j个元素结尾的最长公共子串
  • 状态转移:
    • 当S1[i]==S2[j]时,dp[i][j]==dp[i-1][j-1]+1
    • 当S1[i]!=S2[j]时,dp[i][j]==0
  • 最长公共子串长度为max( dp[i][j] )
int findLength(vector<int>& A, vector<int>& B) {
    int n=A.size(),m=B.size();
    vector<vector<int>> dp(n+1,vector<int>(m+1,0));
    int res=0;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            if(A[i-1]==B[j-1]) dp[i][j]=dp[i-1][j-1]+1;
            else dp[i][j]=0;
            res=max(res,dp[i][j]);
        }
    }
    return res;
}

最长公共子序列问题

简称为LCS(Longest Common Subsequence)问题, 如求两个数组S1,S2的最长的公共的子序列。
题目参考:leedcode-583

  • 定义状态:dp[i][j]表示S1前i个元素与S2前j个元素的最长公共子序列(不一定需要第i/j个元素)
  • 状态转移:
    • 当S1[i]==S2[j],dp[i][j]==dp[i-1][j-1]+1
    • 当S1[i]!=S2[j],dp[i][j]=max(dp[i][j-1],dp[i-1][j])
  • 最大长度为dp[n][m]
int lengthOfLCS(string S1,string S2){
    int n=S1.size();
    int m=S2.size();
    vector<vector<int>> dp(n+1,vector<int>(m+1,0);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(S1[i-1]==S2[j-1]){
                dp[i][j]=dp[i-1][j-1]+1;
            }else{
                dp[i][j]=max(dp[i][j-1],dp[i-1][j]);
            }
        }
    }
    return dp[n][m];
}
;