子串与子序列问题

前言

首先子串!=子序列

  • 子串:需要连续,如{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];
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,这道题目需要求解的是最长公共子串问题,与最长公共子序列问题有所不同。最长公共子串问题可以通过动态规划的方式求解,具体思路如下: 设 $dp[i][j]$ 表示以 $a[i]$、$b[j]$ 结尾的最长公共子串长度,即: $$ dp[i][j] = \begin{cases} 0, & i=0 \text{ 或 } j=0 \\ dp[i-1][j-1]+1, & a[i]=b[j] \\ 0, & a[i] \neq b[j] \end{cases} $$ 其中,当 $a[i]=b[j]$ 时,$dp[i][j]$ 取决于 $dp[i-1][j-1]$,表示此时最长公共子串长度加一;当 $a[i]\neq b[j]$ 时,$dp[i][j]$ 必然为零,因为以 $a[i]$、$b[j]$ 结尾的字符串不可能存在公共子串。 最终答案即为 $dp$ 数组中的最大值。时间复杂度为 $O(n^2)$,其中 $n$ 是字符串长度。 代码如下: ```python def longest_common_substring(a, b): m, n = len(a), len(b) dp = [[0] * (n+1) for _ in range(m+1)] max_len = 0 for i in range(1, m+1): for j in range(1, n+1): if a[i-1] == b[j-1]: dp[i][j] = dp[i-1][j-1] + 1 max_len = max(max_len, dp[i][j]) else: dp[i][j] = 0 return max_len ``` 如果需要输出最长公共子串,可以记录最长公共子串的结束位置,然后通过结束位置和最长公共子串长度计算出起始位置: ```python def longest_common_substring(a, b): m, n = len(a), len(b) dp = [[0] * (n+1) for _ in range(m+1)] end, max_len = 0, 0 for i in range(1, m+1): for j in range(1, n+1): if a[i-1] == b[j-1]: dp[i][j] = dp[i-1][j-1] + 1 if dp[i][j] > max_len: max_len = dp[i][j] end = i - 1 else: dp[i][j] = 0 start = end - max_len + 1 return a[start:end+1] ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值