【刷题】动态规划专题(三)序列类型

剑指 Offer 42. 连续子数组的最大和
输入一个整型数组,数组里有正数也有负数。数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
要求时间复杂度为O(n)。
示例1:
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
提示:
1 <= arr.length <= 10^5
-100 <= arr[i] <= 100

分析:
1.dp[i]:以nums[i]作为结尾的最大连续序列和
2.dp[i] = max(dp[i-1]+nums[i],nums[i])
看当前元素加在上一个最大序列之后得到的和大,还是将当前元素作为一个新的开始得到的和大。
3.比较所有元素作为结尾得到的连续子数组和的大小。
4.初始化:因为至少选择数组中的一个数,所以当只有一个元素时,最大和就是元素的值,dp[0] = nums[0]。

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        vector<int>dp(nums.size(),0);
        dp[0] = nums[0];
        int ans = dp[0];
        for(int i=1;i<nums.size();i++){
            dp[i] = max(nums[i],dp[i-1]+nums[i]);
            ans = max(ans,dp[i]);
        }
        return ans;
    }
};

300. 最长上升子序列
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:
可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

子序列:可以不连续
连续子序列:连续的

分析:
1.dp[i]:以nums[i]作为结尾的最长上升子序列长度
2.假设现在已知dp[0]…dp[i-1] 分别以nums[0]…nums[i-1]作为结尾的最长上升子序列长度,如果想要在加入nums[i]后依然是最长上升子序列,就需要判断两个条件:nums[i]是否大于nums[j],nums[i]加在哪个序列后才能得到最长。
dp[i] = max(dp[0]+1,…,dp[j]+1,…,dp[i-1]+1,1)(nums[j]<nums[i])
同时考虑到nums[i]有可能小于前面所有的元素,所以它自己作为一个序列,也就是序列长度为1.

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        vector<int>dp(nums.size(),0);
        int res = 0;
        for(int i=0;i<nums.size();i++){
            dp[i] = 1;
            for(int j =0;j<i;j++){
                if(nums[i]>nums[j])
                    dp[i] = max(dp[i],dp[j]+1);
            }
            res = max(res,dp[i]);
        }
        return res;
    }
};

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
输入的字符串只含有小写英文字符。

分析:
1.dp[i][j]:以text1[i],text2[j]分别为结尾的序列的最长公共子序列长度。
2.转移方程在这里插入图片描述
3.初始状态:需要注意的是text1[0]和text2[j]的最长公共子序列长度只能是1,也就是说text1[0]和text2[j]相同时,dp[0][j] =1,不同时dp[0][j]=dp[0][j-1]。dp[i][0]同理。

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        //dp[i][j]:以text[i] text[j]作为结尾的最长公共子序列长度
        int m = text1.size(),n=text2.size();
        int dp[m][n];
        memset(dp,0,sizeof(dp));
        if(text1[0]==text2[0])dp[0][0] =1;
        int res = dp[0][0];
        for(int i=1;i<n;i++){
            if(text1[0]==text2[i])
                dp[0][i] = 1;
            else dp[0][i] = dp[0][i-1];
            res = max(res,dp[0][i]);
        }
        for(int i=1;i<m;i++){
            if(text1[i]==text2[0])
                dp[i][0] = 1;
            else dp[i][0] = dp[i-1][0];
            res = max(res,dp[i][0]);
        }
        
        for(int i=1;i<m;i++){
            for(int j = 1;j<n;j++){
                if(text1[i]==text2[j])
                    dp[i][j] = dp[i-1][j-1]+1;
                else dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
                res = max(res,dp[i][j]);
            }
        }
       
        return res;
    }
};

516. 最长回文子序列
给定一个字符串 s ,找到其中最长的回文子序列,并返回该序列的长度。可以假设 s 的最大长度为 1000 。
示例 1:
输入:
“bbbab”
输出:
4
一个可能的最长回文子序列为 “bbbb”。
示例 2:
输入:
“cbbd”
输出:
2
一个可能的最长回文子序列为 “bb”。
提示:
1 <= s.length <= 1000
s 只包含小写英文字母

1.dp[i][j]:以s[i]作为开头,s[j]作为结尾的序列中包含的最长回文子序列长度
*Note:这里的dp[i][j]是指想要求的回文序列包含在是s[i…j]里面的,而不是回文序列就是s[i…j]。而最大连续子序列和最长上升子序列的dp[i]都是A[i]一定在目标序列中。要注意区别。
2.转移方程在这里插入图片描述
3.初始化:单个字母也是回文,所以对角线上的元素为1。而且作为开头的字母在序列中给的下标应该小于结尾的下标,所以矩阵应该是一个上三角矩阵,下三角的部分都初始化为0。
4.计算顺序:从下到上,从左到右。

class Solution {
public:
    int longestPalindromeSubseq(string s) {
        int n = s.size();
        int dp[n][n];//dp[i][j]:以s[i]作为开头,s[j]作为结尾的序列中包含的最长回文子序列长度
        memset(dp,0,sizeof(dp));
        for(int i=0;i<n;i++){
            dp[i][i] = 1;
        }

        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];
    }
};

5. 最长回文子串
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。
示例 2:
输入: “cbbd”
输出: “bb”

这个题和上个题的区别在于,一个是求最长回文串长度一个是求最长回文串,在思路上是一脉相承的。只要在每次比较最长回文长度时,记录下起始位置和长度即可。
1.dp[i][j]:s[i.j]是否是一个回文串。
2.在这里插入图片描述
3.单个字符和两个字符时都可以直接判断,所以初始化不仅要初始化对角线上的,还要初始化(i,i+1)位置上的。
4.计算顺序同上题。

class Solution {
public:
    string longestPalindrome(string s) {
        if(s.size()==0)return "";
        int n = s.size();
        int dp[n][n];
        memset(dp,0,sizeof(dp));
        int len =0,start;
        for(int i=0;i<n;i++){
            dp[i][i] = 1;
            if(i!=n-1&&s[i]==s[i+1]){
                dp[i][i+1] = 1;
            }
            if(dp[i][i]==1&&1>len){
                len = 1;
                start = i;
            }
            if(i!=n-1&&dp[i][i+1]==1&&2>len){
                len = 2;
                start = i;
            }
        }
        for(int i=n-1;i>=0;i--){
            for(int j =i+2;j<n;j++){
                if(s[i]==s[j])
                    dp[i][j] = dp[i+1][j-1];
                if(dp[i][j]==1&&j-i+1>len){
                    len = j-i+1;
                    start = i;
                }
            }
        }
        return s.substr(start,len);
    }
};

*Note:substr()的用法是传入起始位置和截取长度。

139. 单词拆分
给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
说明:
拆分时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。
示例 1:
输入: s = “leetcode”, wordDict = [“leet”, “code”]
输出: true
解释: 返回 true 因为 “leetcode” 可以被拆分成 “leet code”。
示例 2:
输入: s = “applepenapple”, wordDict = [“apple”, “pen”]
输出: true
解释: 返回 true 因为 “applepenapple” 可以被拆分成 “apple pen apple”。
注意你可以重复使用字典中的单词。
示例 3:
输入: s = “catsandog”, wordDict = [“cats”, “dog”, “sand”, “and”, “cat”]
输出: false

1.dp[i]:到第i个字母构成的序列是否满足条件
2.dp[i]=dp[j]&&(s[j+1…i]在单词表中)
3.j代表的含义就是分割序列的位置,j及其之前的序列是否符合条件由上一轮计算得到,所以i是对字符串s的遍历,而j是枚举可能的分割点。
需要注意的是:
i)当已经找到一个合适的分割使dp[i]等于1,就不用再往下循环了,不然就会覆盖正确答案。
ii)j应该小于i,而不是等于i结束内层循环,j=i时需要判断的字符串就是空的,没有必要。
4.初始化:dp[0] = true,dp[0]代表的并不是下标为0的字符是否满足条件,而是一个字符都没有的情况。dp[i]中的i比字符串下标多1,也就是说dp[i]对应的是s[0…i-1],需要判断的是s[j…i-1]。

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        map<string,int>mp;
        for(int i=0;i<wordDict.size();i++){
            mp[wordDict[i]] = 1;
        }
        vector<bool>dp(s.size()+1);//dp[i]:到第i个字母的序列是否满足条件
        dp[0]= true; 
        for(int i=1;i<=s.size();i++){
            for(int j =0;j<i;j++){
                dp[i] = (dp[j]&&mp[s.substr(j,i-j)]==1)?true:false;//j是分割点
                if(dp[i]==1)break;
                //cout<<i<<" "<<j<<" "<<s.substr(j,i-j)<<" "<<dp[i]<<endl;
            }
        }
        return dp[s.size()];
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值