day20动态规划7

文章介绍了多个与动态规划相关的字符串处理问题,如最长递增子序列、最长连续递增序列、最长重复子数组、最长公共子序列、最大子序和、子序列判断、编辑距离和回文子串的求解方法,展示了动态规划在解决这些问题中的应用。
摘要由CSDN通过智能技术生成

300.最长递增子序列

int lengthOfLIS(vector<int>& nums) {
        if(nums.size()<=1) return nums.size();
        vector<int> dp(nums.size(),1);
        int result=0;
        for(int i=1;i<nums.size();i++){
            for(int j=0;j<i;j++){
                if(nums[i]>nums[j]) dp[i]=max(dp[i],dp[j]+1);
            }
            if(dp[i]>result) result=dp[i];// 取长的子序列
        }
        return result;
    }

674. 最长连续递增序列

int findLengthOfLCIS(vector<int>& nums) {
        if(nums.size()<=1) return nums.size();
        vector<int> dp(nums.size(),1);
        int result=0;
        for(int i=1;i<nums.size();i++){
            if(nums[i]>nums[i-1]) // 连续记录
                dp[i]=dp[i-1]+1; 
            if(dp[i]>result) 
                result=dp[i];// 取长的子序列
        }
        return result;
    }

718. 最长重复子数组

1.确定dp数组(dp table)以及下标的含义
dp[i][j] :以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dp[i][j]。 (特别注意: “以下标i - 1为结尾的A” 标明一定是 以A[i-1]为结尾的字符串 )
2.确定递推公式
根据dp[i][j]的定义,dp[i][j]的状态只能由dp[i - 1][j - 1]推导出来。
即当A[i - 1] 和B[j - 1]相等的时候,dp[i][j] = dp[i - 1][j - 1] + 1;
根据递推公式可以看出,遍历i 和 j 要从1开始!
3.dp数组如何初始化
根据dp[i][j]的定义,dp[i][0] 和dp[0][j]其实都是没有意义的!
但dp[i][0] 和dp[0][j]要初始值,因为 为了方便递归公式dp[i][j] = dp[i - 1][j - 1] + 1;
所以dp[i][0] 和dp[0][j]初始化为0。

int findLength(vector<int>& nums1, vector<int>& nums2) {
        vector<vector<int>> dp(nums1.size()+1,vector<int>(nums2.size()+1,0));
        int result=0;
        for(int i=1;i<=nums1.size();i++){
            for(int j=1;j<=nums2.size();j++){
                if(nums1[i-1]==nums2[j-1])
                    dp[i][j]=dp[i-1][j-1]+1;
                if(dp[i][j]>result) 
                    result=dp[i][j];// 取长的子序列
            }   
        }
        return result;
    }
拓展

前面讲了 dp数组为什么定义:以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dp[i][j]。

我就定义dp[i][j]为 以下标i为结尾的A,和以下标j 为结尾的B,最长重复子数组长度。不行么?

当然可以,就是实现起来麻烦一些。

如果定义 dp[i][j]为 以下标i为结尾的A,和以下标j 为结尾的B,那么 第一行和第一列毕竟要进行初始化,如果nums1[i] 与 nums2[0] 相同的话,对应的 dp[i][0]就要初始为1, 因为此时最长重复子数组为1。 nums2[j] 与 nums1[0]相同的话,同理。

int findLength(vector<int>& nums1, vector<int>& nums2) {
        vector<vector<int>> dp (nums1.size() + 1, vector<int>(nums2.size() + 1, 0));
        int result = 0;

        // 要对第一行,第一列经行初始化
        for (int i = 0; i < nums1.size(); i++) if (nums1[i] == nums2[0]) dp[i][0] = 1;
        for (int j = 0; j < nums2.size(); j++) if (nums1[0] == nums2[j]) dp[0][j] = 1;

        for (int i = 0; i < nums1.size(); i++) {
            for (int j = 0; j < nums2.size(); j++) {
                if (nums1[i] == nums2[j] && i > 0 && j > 0) { // 防止 i-1 出现负数
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                }
                if (dp[i][j] > result) result = dp[i][j];
            }
        }
        return result;
    }

在推导出转移方程后可能疑惑上述代码为什么要从i=0,j=0遍历而不是从i=1,j=1开始遍历,原因在于这里如果不是从i=0,j=0位置开始遍历,会漏掉如下样例结果:

nums1 = [70,39,25,40,7]
nums2 = [52,20,67,5,31]

也就是说,一开始都是初始化为0,假如最长地址子序列就是1的话,会漏掉result和dp[0][j] dp[i][0]的比较。

1143.最长公共子序列

本题和动态规划:718. 最长重复子数组 区别在于这里不要求是连续的了,但要有相对顺序,即:“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。

 int longestCommonSubsequence(string text1, string text2) {
        vector<vector<int>> dp(text1.size()+1,vector<int>(text2.size()+1,0));
        for(int i=1;i<=text1.size();i++){
            for(int j=1;j<=text2.size();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[text1.size()][text2.size()];
    }

1035.不相交的线

本题就和动态规划:1143.最长公共子序列 (opens new window)是一样的。

int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {
        vector<vector<int>> dp(nums1.size()+1,vector<int>(nums2.size()+1,0));
        for(int i=1;i<=nums1.size();i++){
            for(int j=1;j<=nums2.size();j++){
                if(nums1[i-1]==nums2[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[nums1.size()][nums2.size()];
    }

53. 最大子序和

nt maxSubArray(vector<int>& nums) {
        if(nums.size()==0)  return 0;
        vector<int> dp(nums.size());
        dp[0]=nums[0];
        int result=dp[0];///不是0
        for(int i=1;i<nums.size();i++){
            dp[i]=max(dp[i-1]+nums[i],nums[i]);
            result = result > dp[i] ? result : dp[i];
        }
        return result;
    }

392.判断子序列

这道题目算是编辑距离的入门题目

dp[i][j] 表示以下标i-1为结尾的字符串s,和以下标j-1为结尾的字符串t,相同子序列的长度为dp[i][j]。
确定递推公式
在确定递推公式的时候,首先要考虑如下两种操作,整理如下:

if (s[i - 1] == t[j - 1])
t中找到了一个字符在s中也出现了
if (s[i - 1] != t[j - 1])
相当于t要删除元素,继续匹配
if (s[i - 1] == t[j - 1]),那么dp[i][j] = dp[i - 1][j - 1] + 1;,因为找到了一个相同的字符,相同子序列长度自然要在dp[i-1][j-1]的基础上加1(如果不理解,在回看一下dp[i][j]的定义)

if (s[i - 1] != t[j - 1]),此时相当于t要删除元素,t如果把当前元素t[j - 1]删除,那么dp[i][j] 的数值就是 看s[i - 1]与 t[j - 2]的比较结果了,即:dp[i][j] = dp[i][j - 1];

其实这里 和 1143.最长公共子序列 (opens new window)的递推公式基本那就是一样的,区别就是 本题 如果删元素一定是字符串t,而 1143.最长公共子序列 是两个字符串都可以删元素。

dp数组如何初始化
从递推公式可以看出dp[i][j]都是依赖于dp[i - 1][j - 1] 和 dp[i][j - 1],所以dp[0][0]和dp[i][0]是一定要初始化的。

bool isSubsequence(string s, string t) {
        vector<vector<int>> dp(s.size()+1,vector<int>(t.size()+1,0));
        for(int i=1;i<=s.size();i++){
            for(int j=1;j<=t.size();j++){
                if(s[i-1]==t[j-1])
                    dp[i][j]=dp[i-1][j-1]+1;
                else
                    dp[i][j]=dp[i][j-1];
            }   
        }
        return dp[s.size()][t.size()]==s.size();
    }

115.不同的子序列

int numDistinct(string s, string t) {
        //vector<vector<int>> dp(s.size()+1,vector<int>(t.size()+1));
        vector<vector<uint64_t>> dp(s.size()+1,vector<uint64_t>(t.size()+1));
        //vector<vector<long long>> dp(s.size() + 1, vector<long long>(t.size() + 1));
        for(int i=0;i<=s.size();i++){
            dp[i][0]=1;
        }
        for(int j=1;j<=t.size();j++){
            dp[0][j]=0;
        }
        for(int i=1;i<=s.size();i++){
            for(int j=1;j<=t.size();j++){
                if(s[i-1]==t[j-1])
                    dp[i][j]=dp[i-1][j-1]+dp[i-1][j];
                else
                    dp[i][j]=dp[i-1][j];
            }   
        }
        return dp[s.size()][t.size()];
    }

583. 两个字符串的删除操作

int minDistance(string word1, string word2) {
        vector<vector<int>> dp(word1.size()+1,vector<int>(word2.size()+1));
        for(int i=0;i<=word1.size();i++) dp[i][0]=i;
        for(int j=0;j<=word2.size();j++) dp[0][j]=j;
        for(int i=1;i<=word1.size();i++){
            for(int j=1;j<=word2.size();j++){
                if(word1[i-1]==word2[j-1])
                    dp[i][j]=dp[i-1][j-1];
                else
                    dp[i][j]=min(min(dp[i-1][j]+1,dp[i][j-1]+1),dp[i-1][j-1]+2);
            }
        }
        return dp[word1.size()][word2.size()];
    }
思路2:

多余元素个数(删除操作)=word1.size()+ word2.size() -2*最长公共子序列
1143.最长公共子序列

int minDistance(string word1, string word2) {
        vector<vector<int>> dp(word1.size()+1,vector<int>(word2.size()+1,0));
        for(int i=1;i<=word1.size();i++){
            for(int j=1;j<=word2.size();j++){
                if(word1[i-1]==word2[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 word1.size()+ word2.size() -2*dp[word1.size()][word2.size()];
    }

72. 编辑距离

int minDistance(string word1, string word2) {
        vector<vector<int>> dp(word1.size()+1,vector<int>(word2.size()+1,0));
        for(int i=0;i<=word1.size();i++) dp[i][0]=i;
        for(int j=0;j<=word2.size();j++) dp[0][j]=j;
        for(int i=1;i<=word1.size();i++){
            for(int j=1;j<=word2.size();j++){
                if(word1[i-1]==word2[j-1])
                    dp[i][j]=dp[i-1][j-1];
                else
                    dp[i][j]=min(min(dp[i-1][j]+1,dp[i][j-1]+1),dp[i-1][j-1]+1);
                    //dp[i][j] = min({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1;
            }
        }
        return dp[word1.size()][word2.size()];
    }

647. 回文子串

int countSubstrings(string s) {
        vector<vector<bool>> dp(s.size()+1,vector<bool>(s.size()+1,false));
        int result=0;
        for(int i=s.size()-1;i>=0;i--){ 注意遍历顺序
            for(int j=i;j<s.size();j++){
                if(s[i]==s[j]){
                    if(j-i<=1){// 情况一 和 情况二
                        dp[i][j]=true;
                        result++;
                    }else if(dp[i+1][j-1] == true){// 情况三
                        dp[i][j]=true;
                        result++;
                    }
                }
            }   
        }
        return result;
    }

双指针法
动态规划的空间复杂度是偏高的,我们再看一下双指针法。
首先确定回文串,就是找中心然后向两边扩散看是不是对称的就可以了。
在遍历中心点的时候,要注意中心点有两种情况。
一个元素可以作为中心点,两个元素也可以作为中心点。
那么有人同学问了,三个元素还可以做中心点呢。其实三个元素就可以由一个元素左右添加元素得到,四个元素则可以由两个元素左右添加元素得到.
所以我们在计算的时候,要注意一个元素为中心点和两个元素为中心点的情况。
这两种情况可以放在一起计算,但分别计算思路更清晰,我倾向于分别计算,代码如下:

int countSubstrings(string s) {
        int result = 0;
        for (int i = 0; i < s.size(); i++) {
            result += extend(s, i, i, s.size()); // 以i为中心
            result += extend(s, i, i + 1, s.size()); // 以i和i+1为中心
        }
        return result;
    }
    int extend(const string& s, int i, int j, int n) {
        int res = 0;
        while (i >= 0 && j < n && s[i] == s[j]) {
            i--;
            j++;
            res++;
        }
        return res;
    }

516.最长回文子序列

int longestPalindromeSubseq(string s) {
        vector<vector<int>> dp(s.size(),vector<int>(s.size(),0));
        for(int i=0; i<s.size();i++) dp[i][i]=1;
        for(int i=s.size()-1;i>=0;i--){ 注意遍历顺序
            for(int j=i+1;j<s.size();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][s.size()-1];
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值