动态规划-最长公共子序列,最长公共子串,最长上升子序列

一、最长公共子序列

给定两个字符串str1和str2,输出两个字符串的最长公共子序列。如果最长公共子序列为空,则返回"-1"。目前给出的数据,仅仅会存在一个最长的公共子序列。最终返回的子序列在原字符串中可以不连续。

1.确定dp数组的含义

dp[i][j]:下标为i-1的字符串str1与下标为j-1的字符串str2的最长公共子序列的长度。
1.当找到最长公共子序列的长度之后dp[str1.size()][str2.size()],如果为0,则代表不存在公共子序列,返回-1即可
2.再从将两个字符串从后往前看,如果加一个str1中的元素,对最长公共子序列无影响,则str1继续前移,或者加str2中的元素无影响,则str2前移,其他情况下,说明找到一个公共子序列,加入到结果中,两个字符串同时前移。整个过程直到两个字符串都为空即可全部找完
3.最后在结果数组中反转一下即可

2.dp的状态转移公式

如果str1[i-1]与str2[j-1]相等,那么dp[i][j]=dp[i-1][j-1]+1;
如果str1[i-1]与str2[j-1]不相等,那么就看str1从[0,i-2],str2从[0,j-1]的最长公共子序列即dp[i-1][j]和str1从[0,i-1],str2从[0,j-2]的最长公共子序列即dp[i][j-1],因为题目中要求取最长子序列,那么就取两者的最大值即可

2.dp数组初始化

dp数组整体初始化为0即可

3.确定遍历顺序

由状态转移公式可知,dp[i][j]由dp[i-1][j-1]、dp[i][j-1]、dp[i-1][j]推导而来,所以均从小到大遍历即可

4.代码

class Solution {
public:
    string LCS(string s1, string s2) {
       // dp[i][j]:长度为[0, i - 1]的字符串text1与长度为[0, j - 1]的字符串text2的最长公共子序列为dp[i][j]
        vector<vector<int>>dp(s1.size()+1,vector<int>(s2.size()+1,0));
        for(int i = 1; i<=s1.size(); i++){
            for(int j = 1; j<=s2.size(); j++){
                if(s1[i-1]==s2[j-1]){
                    dp[i][j] = dp[i-1][j-1]+1;
                }else{
                    dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        if(dp[s1.size()][s2.size()]==0) return "-1";
        string ans;
        int x = s1.size(),y = s2.size();
        while(x!=0&&y!=0){
            if(dp[x][y]==dp[x-1][y]){//说明增加了一个s1的元素对最长序列没有影响,说明当前值不是属于公共的部分
                x--;
            }else if(dp[x][y]==dp[x][y-1]){
                y--;
            }else{
                ans+=s1[x-1];
                x--;
                y--;
            }
        }
        reverse(ans.begin(),ans.end());//因为是逆序找的,所以最后要反转一下才可以
        return ans;
        
    }
};

二、最长公共子串

给定两个字符串str1和str2,输出两个字符串的最长公共子串
题目保证str1和str2的最长公共子串存在且唯一。
与上个题的区别在于,这个题里面的找的公共子串是连续的,而上个题是不连续的,所以我们的递推公式只用考虑str1[i-1]和str2[j-1]相等的情况,因为如果不相等的话也就跟前面的不连续了,直接跳过本层循环,开始找下一个即可。

1.确定dp数组的含义

dp[i][j]:下标为i-1的字符串str1与下标为j-1的字符串str2的最长公共子串的长度。

2.dp的状态转移公式

str1[i-1]与str2[j-1]相等,那么dp[i][j]=dp[i-1][j-1]+1;
此时即可与之前记录的最长子串做比较,如果大于之前的,那么当前值即为最长子串长度,记录当前下标,当前下标-最长子串长度即为在原始字符串中相等开始的位置,从原始字符串中,在开始位置截取最长子串长度即为最终结果。

2.dp数组初始化

dp数组初始为0即可

3.确定遍历顺序

由状态转移公式可知,dp[i][j]由dp[i-1][j-1]推导而来,所以均从小到大遍历即可

4.代码

class Solution {
public:
    string LCS(string str1, string str2) {
        // write code here
        int n = str1.size();
        int m = str2.size();
        int maxlen = 0;
        int index = 0;
        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(str1[i-1]==str2[j-1]){
                    dp[i][j] = dp[i-1][j-1]+1;
                    if(maxlen<dp[i][j]){
                        maxlen = dp[i][j];//与不连续的区别之处就是在这里,记录最大的连续子串,并记录出
                        //现的位置下标,当前位置下标减去最大连续子串,就是子串的起始位置,从str1里面截取即可
                        index = i-1;
                }
            }
            }
        }
        int start_index = index-maxlen+1;//而不连续的子串则需要一个一个去遍历
        return str1.substr(start_index,maxlen);
    }
};

三、最长上升子序列

给定一个长度为 n 的数组 arr,求它的最长严格上升子序列的长度。
所谓子序列,指一个数组删掉一些数(也可以不删)之后,形成的新数组。例如 [1,5,3,7,3] 数组,其子序列有:[1,3,3]、[7] 等。但 [1,6]、[1,3,5] 则不是它的子序列。

1.确定dp数组的含义

dp[i]表示i之前包括i的以arr[i]结尾最长上升子序列的长度

2.dp的状态转移公式

位置i的最长升序子序列等于j从0到i-1各个位置的最长升序子序列 + 1 的最大值。
所以这里用到了两层遍历,i从外层遍历整个数组,j从内部遍历i之间的所有元素,arr[i]看是否大于arr[j]
所以:if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
注意这里不是要dp[i] 与 dp[j] + 1进行比较,而是要取每一层外部遍历的dp[j] + 1的最大值。

2.dp数组初始化

dp[0]代表arr[0]的最长上升子序列,则为1,所以最长上升子序列最少为1,初始化为1即可

3.确定遍历顺序

i是从数组头遍历到数组尾,从小到大遍历
j是从0遍历到i-1,也为正序,即从小到大

4.代码

class Solution {
public:
    int LIS(vector<int>& arr) {
        // write code here
        if(arr.size()<=1) return arr.size();
        vector<int>dp(arr.size(),1);
        int result = 0;
        dp[0] = 1;
        for(int i = 1; i<arr.size(); i++){
            for(int j = 0; j<i; j++){
                if(arr[i]>arr[j]){
                    dp[i] = max(dp[i],dp[j]+1);//不断判断在(0,i-1)这段区间内第i个值比前面值大的个数,最后取最大值
                    if(dp[i]>result){
                        result = dp[i];
            }
                }
            }
            
        }
        return result;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值