子序列&&子串

本文参考作者:labuladong

两种思路

1、第一种思路模板是一个一维的 dp 数组:

int n = array.length;
int[] dp = new int[n];

for (int i = 1; i < n; i++) {
    for (int j = 0; j < i; j++) {
        dp[i] = 最值(dp[i], dp[j] + ...)
    }
}

例如「最长递增子序列」,在这个思路中 dp 数组的定义是:

在子数组 array[0…i] 中,我们要求的子序列(最长递增子序列)的长度是 dp[i]。

2、第二种思路模板是一个二维的 dp 数组:

int n = arr.length;
int[][] dp = new dp[n][n];

for (int i = 0; i < n; i++) {
    for (int j = 0; j < n; j++) {
        if (arr[i] == arr[j]) 
            dp[i][j] = dp[i][j] + ...
        else
            dp[i][j] = 最值(...)
    }
}

这种思路运用相对更多一些,尤其是涉及两个字符串/数组的子序列,比如「最长公共子序列」。本思路中 dp 数组含义又分为「只涉及一个字符串」和「涉及两个字符串」两种情况。

2.1 涉及两个字符串/数组时(比如最长公共子序列),dp 数组的含义如下:

在子数组 arr1[0…i] 和子数组 arr2[0…j] 中,我们要求的子序列(最长公共子序列)长度为 dp[i][j]。

2.2 只涉及一个字符串/数组时(比如本文要讲的最长回文子序列),dp 数组的含义如下:

在子数组 array[i…j] 中,我们要求的子序列(最长回文子序列)的长度为 dp[i][j]。

300. 最长上升子序列

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例:

输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:

可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

//动态规划
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        vector<int> F(nums.size(),1);//F[i]表示以nums[i]结尾的最长公共子序列的长度
        for(int i=1,j;i<nums.size();i++){
            for(j=i-1;j>=0;j--){
                if(nums[j]<nums[i]&&F[i]<F[j]+1){
                    F[i]=F[j]+1;
                }
            }
        }
        int max=0;
        for(int i=0;i<F.size();i++){
            if(F[i]>max){
                max=F[i];
            }
        }
        return max;
    }
};

时间复杂度:O(N^2),这里 N 是数组的长度,我们写了两个 for 循环,每个 for 循环的时间复杂度都是线性的。
空间复杂度:O(N),要使用和输入数组长度相等的状态数组,因此空间复杂度是 O(N)。

链接
来源:力扣(LeetCode)

673. 最长递增子序列的个数

给定一个未排序的整数数组,找到最长递增子序列的个数。

示例 1:

输入: [1,3,5,4,7]
输出: 2
解释: 有两个最长递增子序列,分别是 [1, 3, 4, 7] 和[1, 3, 5, 7]。
示例 2:

输入: [2,2,2,2,2]
输出: 5
解释: 最长递增子序列的长度是1,并且存在5个子序列的长度为1,因此输出5。
注意: 给定的数组长度不超过 2000 并且结果一定是32位有符号整数。

class Solution {
public:
    int findNumberOfLIS(vector<int>& nums) {
        vector<int> count(nums.size(),1);
        vector<int> num_count(nums.size(),1);
        //count[i]表示以nums[i]为结尾的最长递增子序列的长度
        //num_count表示以nums[i]为结尾的最长递增子序列的个数(注:不是最长子序列的个数)
        for(int i=1;i<nums.size();i++){
            for(int j=i-1;j>=0;j--){
                if(nums[i]>nums[j]&&count[i]<count[j]+1){
                    count[i]=count[j]+1;
                    num_count[i]=num_count[j];//发现更长的子序列
                }
                else if(nums[i]>nums[j]&&count[i]==count[j]+1){
                    num_count[i]+=num_count[j];//发现长度相同的子序列
                }
            }
        }
        int loc_max=0,ret=0;
        for(int i=0;i<count.size();i++){
            if(count[loc_max]<count[i]){
                loc_max=i;
            }
            
        }
        for(int i=0;i<count.size();i++){
            if(count[loc_max]==count[i]){
                ret+=num_count[i];
            }
            
        }
        //return num_count[loc_max];//错误
        return ret;
    }
};

来源:力扣(LeetCode)
链接

516. 最长回文子序列

给定一个字符串 s ,找到其中最长的回文子序列,并返回该序列的长度。可以假设 s 的最大长度为 1000 。

示例 1:
输入:

"bbbab"
输出:

4
一个可能的最长回文子序列为 "bbbb"。

示例 2:
输入:

"cbbd"
输出:

2
一个可能的最长回文子序列为 "bb"。


提示:
- 1 <= s.length <= 1000
- s 只包含小写英文字母

在这里插入图片描述

class Solution {
public:
    int longestPalindromeSubseq(string s) {
        //区间规划
        int n=s.size();
        vector<vector<int>> dp(n,vector<int>(n,0));
        //dp[i][j]表示s[i]~s[j]之间的最长回文子序列
        for(int i=0;i<n;i++){
            dp[i][i]=1;
        }
        //为了保证每次计算 dp[i][j],左下右方向的位置已经被计算出来,只能斜着遍历或者反着遍历:
        //反着遍历保证正确的状态转移
        for(int i=n-1;i>=0;i--){
            for(int j=i+1;j<n;j++){
                //状态转移方程
                if(s[i]==s[j]){
                    dp[i][j]=dp[i+1][j-1]+2;
                }
                else{
                    dp[i][j]=max(dp[i+1][j],dp[i][j-1]);
                }
            }
        }
        return dp[0][n-1];
    }
};

1143. 最长公共子序列

给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。

若这两个字符串没有公共子序列,则返回 0。


示例 1:

输入:text1 = "abcde", text2 = "ace" 
输出:3  
解释:最长公共子序列是 "ace",它的长度为 3。
示例 2:

输入:text1 = "abc", text2 = "abc"
输出:3
解释:最长公共子序列是 "abc",它的长度为 3。
示例 3:

输入:text1 = "abc", text2 = "def"
输出:0
解释:两个字符串没有公共子序列,返回 0。

提示:

1 <= text1.length <= 1000
1 <= text2.length <= 1000
输入的字符串只含有小写英文字符。
class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        int m=text1.size(),n=text2.size();
        vector<vector<int>> dp(m+1,vector<int>(n+1,0));
        //dp[i][j]表示text1[0~i-1]和text2[0~j-1]的最长公共子序列
        for(int i=1;i<=m;i++){
            for(int j=1;j<=n;j++){
                if(text1[i-1]==text2[j-1]){
                    dp[i][j]=dp[i-1][j-1]+1;
                }
                else{
                    dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        return dp[m][n];

    }
};

5. 最长回文子串

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。
示例 2:

输入: “cbbd”
输出: “bb”

//法一:动态规划法
class Solution {
public:
    string longestPalindrome(string s) {
        string ans;
        int N=s.size();
        vector<vector<bool>> dp(N,vector<bool>(N,false));
        //dp[i][j]=true表示si...sj为回文子串
        //从长度较短的字符串向长度较长的字符串转移
        for(int l=0;l<N;++l){
            for(int i=0;i+l<N;++i){
                int j=i+l;
                if(l==0){
                    dp[i][j]=true;
                }
                else if(l==1){
                    dp[i][j]=(s[i]==s[j]);
                }
                else{
                    dp[i][j]=dp[i+1][j-1]&&(s[i]==s[j]);
                }
                if(dp[i][j]&&l+1>ans.size()){
                    ans=s.substr(i,l+1);
                }
            }
        }
        return ans;
    }
};
//法二:中心扩展算法
/*枚举所有的「回文中心」并尝试「扩展」,直到无法扩展为止,此时的回文串长度即为此「回文中心」下的最长回文串长度。*/
class Solution {
public:
    pair<int, int> expandAroundCenter(const string& s, int left, int right) {
        while (left >= 0 && right < s.size() && s[left] == s[right]) {
            --left;
            ++right;
        }
        return {left + 1, right - 1};
    }

    string longestPalindrome(string s) {
        int start = 0, end = 0;
        for (int i = 0; i < s.size(); ++i) {
            auto [left1, right1] = expandAroundCenter(s, i, i);
            auto [left2, right2] = expandAroundCenter(s, i, i + 1);
            if (right1 - left1 > end - start) {
                start = left1;
                end = right1;
            }
            if (right2 - left2 > end - start) {
                start = left2;
                end = right2;
            }
        }
        return s.substr(start, end - start + 1);
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值