Leetcode刷题笔记Day18:动态规划Ⅲ

89 阅读5分钟

最长递增子序列

  • 力扣题目链接
  • 子序列是可以删除元素但不改变元素相对顺序的一种序列
  • 非常适合使用dp来解决问题
int lengthOfLIS(vector<int>& nums) {
    vector<int> dp(nums.size(), 1);
    int result=1;
    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;
}

最长连续递增序列

int findLengthOfLCIS(vector<int>& nums) {
    vector<int> dp(nums.size(), 1);
    int result=1;
    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;
}

最长重复子数组

  • 力扣题目链接
  • 明确dp[i][j]的含义,以i-1和j-1结尾的最长重复子数组
  • 注意这里的子序列是连续的!
int findLength(vector<int>& nums1, vector<int>& nums2) {
    // dp[i][j]的含义是以i-1和j-1结尾的数组的最长重复子数组长度
    vector<vector<int>> dp(nums1.size()+1, 
                           vector<int>(nums2.size()+1, 0));
    // 用result是因为dp.back()不一定是最长的,可能在中间取得
    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;
}

最长公共子序列

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

不相交的线

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

最大子序和

int maxSubArray(vector<int>& nums) {
    if (nums.size() == 0) return 0;
    vector<int> dp(nums.size(), 0); // dp[i]表示包括i前的最大连续子序列和
    dp[0] = nums[0];
    int result=dp[0];
    for(int i=1; i<nums.size(); i++){
        dp[i]=max(dp[i-1]+nums[i], nums[i]);
        if(dp[i]>result) result=dp[i];
    }
    return result;
}

判断子序列

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;
    // 相当于t要删除元素t[j-1],注意这里的区别
    else dp[i][j]=dp[i][j-1];
    if(dp[s.size()][t.size()]==s.size()) return true;
    else return false;
}
  • 当然,双指针法也秒了:
bool isSubsequence(string s, string t) {
    if(s.empty()) return true;
    int i=0, j=0;
    while(j<t.size()){
        if(s[i]!=t[j]) j++;
        else i++, j++;
        if(i==s.size()) return true;
    }
    return false;
}

不同的子序列

  • 力扣题目链接
  • 注意这里处理字符相等时的不同,加上删除s的字符后出现的次数
  • 注意初始化的不同
int numDistinct(string s, string t) {
    vector<vector<uint64_t>> dp(s.size()+1, 
                                vector<uint64_t>(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];
    // 模拟在s中删除这个元素
    else dp[i][j]=dp[i-1][j];
    return dp[s.size()][t.size()];
}

两个字符串的删除操作

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

编辑距离

  • 力扣题目链接
  • 上面所有的题目都是在为这道题目铺垫
  • 字符相同无需操作,不相同,增删改里面找最小的!
  • 特别需要注意初始化的重大区别!决定本题的关键!!
int minDistance(string word1, string word2) {
    int m=word1.size()+1, n=word2.size()+1;
    vector<vector<int>> dp(m, vector<int>(n));
    for(int i=0; i<m; i++) dp[i][0]=i;
    for(int j=0; j<n; j++) dp[0][j]=j;
    for(int i=1; i<m; i++)
        for(int j=1; j<n; j++)
            if(word1[i-1]==word2[j-1]) dp[i][j]=dp[i-1][j-1];
    else dp[i][j]=min({dp[i-1][j], dp[i][j-1], dp[i-1][j-1]})+1;
    return dp[m-1][n-1];
}

回文子串

  • 力扣题目链接
  • 区间dp的题目,需要仔细揣摩,dp[i][j]代表左闭右闭的区间
int countSubstrings(string s) {
    int n=s.size();
    vector<vector<bool>> dp(n, vector<bool>(n, false));
    int result=0;
    // 注意遍历顺序
    for(int i=n-1; i>=0; i--)
        for(int j=i; j<n; j++)
            if(s[i]==s[j])
                if(j-i<=1 || dp[i+1][j-1]) {
                    result++; dp[i][j]=true;
                }
    return result;
}

最长回文子序列

int longestPalindromeSubseq(string s) {
    int n=s.size();
    vector<vector<int>> dp(n, vector<int>(n, 0));
    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];
}

二叉树中的最大路径和

  • 力扣题目链接
  • 贴一道树形dp的题目,其实dp无所谓什么形式,只是对dp数组解读不同罢了
int result=INT_MIN;
int dfs(TreeNode* root){
    if(root==nullptr) return 0;
    int left_max=max(dfs(root->left), 0);
    int right_max=max(dfs(root->right), 0);
    // 包含当前节点和左右子树的最大路径和
    int cur_max=left_max+right_max+root->val;
    result=max(cur_max, result);
    // 返回经过root的单边最大分支给当前root的父节点计算使用
    return max(left_max, right_max)+root->val;
}
int maxPathSum(TreeNode* root) {
    dfs(root);
    return result;
}

总结

  • 动态规划入门不难,但后面真的是难难难!想有多难可以有多难
  • 更新比较仓促,主要为考研复试做准备,更完这期就去参加复试了,加油!

参考资料

[1] 代码随想录

[2] Leetcode题解

[3] Leetcode算法通关手册