王清欢Randy 王清欢Randy
首页
  • 编程语言

    • C/C++ 学习笔记
    • Golang 学习笔记
  • 算法分析

    • LeetCode 刷题笔记
  • 操作系统

    • Linux 基础
    • Vim 实用技巧
    • Shell 脚本编程
    • GDB 学习笔记
  • 开发工具

    • Git 学习笔记
  • 分布式理论

    • 共识算法
    • 分布式事务
  • 数据库内核

    • PostgreSQL
    • Postgres-XL
  • hidb
  • pgproxy
  • 实用技巧
  • 学习方法
  • 资源分享
GitHub (opens new window)
首页
  • 编程语言

    • C/C++ 学习笔记
    • Golang 学习笔记
  • 算法分析

    • LeetCode 刷题笔记
  • 操作系统

    • Linux 基础
    • Vim 实用技巧
    • Shell 脚本编程
    • GDB 学习笔记
  • 开发工具

    • Git 学习笔记
  • 分布式理论

    • 共识算法
    • 分布式事务
  • 数据库内核

    • PostgreSQL
    • Postgres-XL
  • hidb
  • pgproxy
  • 实用技巧
  • 学习方法
  • 资源分享
GitHub (opens new window)
  • 基础算法

    • 双指针

      • 双指针基础
      • 碰撞指针
      • 快慢指针
      • 滑动窗口
    • 二分查找

      • 基础应用
      • 边界收缩
      • 局部有序
    • 排序算法

      • 01八大排序
      • 02快速排序
      • 03归并排序
      • 04桶排序
      • 05堆排序
    • 优先搜索

      • 01递归
      • 02网格结构深度优先搜索
      • 03树结构深度优先搜索
      • 04图结构深度优先搜索
      • 05网格结构广度优先搜索
      • 06树结构广度优先搜索
      • 07图结构广度优先搜索
    • 回溯算法

      • 01递归
      • 02 子集问题
      • 03 全排列问题
      • 04 组合问题
      • 05 回溯搜索问题
  • 基础数据结构

    • 线性表与哈希表

      • 01数组
      • 02栈和队列
      • 03单调栈
      • 04优先队列
      • 05双端队列
      • 06哈希表
      • 07多重集合
      • 08前缀和
      • 09数据结构设计
    • 字符串

      • 01字符串比较
      • 02回文字符串
      • 03字符串匹配
      • 04字符串算术表达式
    • 单链表

      • 01链表基础操作
      • 02链表遍历
    • 二叉树

      • 01二叉树的属性
      • 02二叉树的操作
      • 03层次遍历
      • 04前中后序遍历
      • 05二叉搜索树的属性
      • 06二叉搜索树的操作
      • 07字典树
      • 08二叉搜索树BST
    • 图

      • 01二分图
      • 02拓扑排序
      • 03并查集
      • 04最小生成树
      • 05最短路径
  • 进阶算法

    • 贪心算法

      • 01跳跃游戏
      • 02分配问题
      • 03区间问题
    • 分治策略

    • 动态规划

      • 01一维动态规划
      • 02二维动态规划
      • 03分割型动态规划
      • 04子序列问题
        • 04 子序列问题
          • 300 最长递增子序列
          • 646 最长数对链
          • 376 摆动序列
          • 1143 最长公共子序列
          • 583 两个字符串的删除操作
      • 05背包问题
      • 06字符串编辑问题
      • 07股票交易问题
  • 其他内容

    • 数学问题

      • 01公倍数与公因数
      • 02质数问题
      • 03进制转换问题
      • 04数字字符串求和问题
      • 05众数问题
      • 06中位数问题
      • 07数字处理问题
      • 08随机数问题
    • 位运算

      • 01位运算基础
      • 02妙用异或运算
      • 03二进制特性
  • LeetCode刷题笔记
  • 进阶算法
  • 动态规划
王清欢
2023-03-24
目录

04子序列问题

# 04 子序列问题

​ 对于子序列问题,第一种动态规划方法是,定义一个 dp 数组,其中 dp[i]表示以 i 结尾的子序列的性质。在处理好每个位置后,统计一遍各个位置的结果即可得到题目要求的结果。第二种动态规划方法是,定义一个 dp数组,其中 dp[i] 表示到位置 i 为止的子序列的性质,并不必须以 i 结尾。这样 dp 数组的最后一位结果即为题目所求,不需要再对每个位置进行统计。

# 300 最长递增子序列 (opens new window)

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

输入是一个一维数组,输出是一个正整数,表示最长递增子序列的长度。

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

解析:

​ 设置状态:使用一个一维数组dp[i] 可以表示以 i 结尾的、最长子序列长度。

​ 状态转移方程:对于每一个位置 i,如果其之前的某个位置 j 所对应的数字小于位置 i 所对应的数字,则可以获得一个以 i 结尾的、长度为 dp[j] + 1 的子序列,即dp[i] = max(dp[i],dp[j]+1)。为了遍历所有情况,我们需要 i 和 j 进行两层循环,并记录最大值。

​ 初始情况:dp[0] = 0, dp[1] = 1

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int len = nums.size();
        // 将所有位置初始化为 1, 因为len>=1,所以最短递增子序列就是 1 
        vector<int> dp(len,1); 
        int ans = 1;
        // i 控制每一个记录位置
        for(int i=1;i<len;++i){ 
            // j 扫描 i 之前的子序列
            for(int j=0;j<i;++j){ 
                // 如果出现递增情况,判断是否增加的子序列长度
                if(nums[i]>nums[j]){ 
                    dp[i] = max(dp[i],dp[j]+1);
                }
            }
            // 完成一个位置的记录之后,判断是否是较大长度
            ans = max(ans,dp[i]);
        }
        return ans;
    }
};

# 646 最长数对链 (opens new window)

给出 n 个数对,在每一个数对中,第一个数字总是比第二个数字小。对于数对(a,b),(c,d)如果 b < c则这两个数对可以构成数对链

输入一个数对集合,输出一个整数表示能够形成的最长数对链的长度。

输入:[[1,2], [2,3], [3,4]]
输出:2
解释:最长的数对链是 [1,2] -> [3,4]

解析:

​ 本题和300题本质上是一样的,不同在于递增序列由数对构成。另外需要考虑数对出现的次序并不影响它是否能够加入数对链,所以在使用类似于300题的动态规划方法计算最长数对链长度之前需要对其进行排序,简化元素选择。本题中以区间(数对)结尾排序。

class Solution {
public:
    int findLongestChain(vector<vector<int>>& pairs) {
        int len = pairs.size();
        if(len < 1) return 0; 
        sort(pairs.begin(),pairs.end(),[](vector<int> a, vector<int> b){ return a[1]<b[1];});
        vector<int> dp(len,1);
        int ans = 1;
        for(int i=1;i<len;++i){
            for(int j=0;j<i;++j){
                if(pairs[j][1]<pairs[i][0]){
                    dp[i] = max(dp[i],dp[j]+1);
                }
            }
            ans = max(ans,dp[i]);
        }
        return ans;
    }
};

# 376 摆动序列 (opens new window)

如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。给你一个整数数组 nums ,返回 nums 中作为 摆动序列 的 最长子序列的长度 。

输入是一个一维数组,输出是一个正整数,表示作为摆动序列的最长子序列的长度。

输入:nums = [1,17,5,10,13,15,10,5,16,8] 输出:7 解释:这个序列包含几个长度为 7 摆动序列。其中一个是 [1, 17, 10, 13, 10, 16, 8] ,各元素之间的差值为 (16, -7, 3, -3, 6, -8) 。

解析:

​ 本题和递增子序列的差别在于摆动序列的元素选择取决于前一个被选元素的大小,既可以上升也可以下降。

​ 设置状态:使用一个二维数组dp[i][j]表示前 i 个元素中的某一个为结尾的最长摆动序列长度,其中 j 取值为 0 和 1分别表示上升摆动序列和下降摆动序列。上升摆动序列是指最后一个元素是呈上升趋势的摆动序列,下降摆动序列同理。d[i][0]表示以前 i 个元素中的某一个为结尾的最长的上升摆动序列的长度;dp[i][1]以前 i 个元素中的某一个为结尾的最长的下降摆动序列的长度。

​ 状态转移方程:考虑第 i 个元素,如果nums[i] > nums[i-1],则该元素可以加入下降摆动序列形成上升摆动序列dp[i][0] = max(dp[i-1][0],dp[i-1][1] + 1),但是该元素加入上升摆动序列并不会形成下降摆动序列dp[i][1] = dp[i-1][1];如果nums[i] < nums[i-1],则该元素可以加入上升摆动序列形成下降摆动序列dp[i][1] = max(dp[i-1][1],dp[i-1][0] + 1),如果该元素加入下降摆动序列并不会形成上升摆动序列所以dp[i][0] = dp[i-1][0];如果nums[i] == nums[i-1]那么可以直接跳过该元素dp[i][0] = dp[i-1][0], dp[i][1] = dp[i-1][1]。

​ 初始情况:当只有一个元素时,该元素既是上升摆动序列也是下降摆动序列,dp[0][j] = 1;

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

​ 本题也可以采用贪心策略解决,使用极值构成最长摆动序列。

# 1143 最长公共子序列 (opens new window)

给定两个字符串,求它们最长的公共子序列长度。

输入是两个字符串,输出是一个整数,表示它们满足题目条件的长度。

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

解析:

​ 设置状态:建立一个二维数组 dp,其中 dp[i][j] 表示到第一个字符串位置 i 为止、到第二个字符串位置 j 为止、最长的公共子序列长度。

​ 状态转移方程:从1开始计算字符串位置,那么当text1 的第 i 个字符与 text2 的第 j 个字符相等时,其最长公共子串长度为前一状态加1,即 dp[i][j] = dp[i-1][j-1] + 1;如果不相等,dp[i][j] 不会比dp[i-1][j] 和dp[i][j-1] 两者之中任何一个小,也不会比两者都大,即 dp[i][j] = max(dp[i-1][j],dp[i][j-1])

​ 初始情况:任一个字符串为空串则最长公共子序列的长度都为0,即dp[0][j] = dp[i][0] = 0

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        int len1 = text1.length(), len2 = text2.length();
        // 创建len+1行len2+1列的二维数组 dp,其中 dp[i][j] 表示 text1[0:i] 和 text2[0:j] 的最长公共子序列的长度
        vector<vector<int>> dp(len1+1,vector<int>(len2+1));
        for(int i=1;i<=len1;++i){
            for(int j=1;j<=len2;++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[len1][len2];
    }
};

# 583 两个字符串的删除操作 (opens new window)

给定两个字符串,求它们通过删除操作变成相同的最小步骤。

输入是两个字符串,输出是一个整数,表示它们通过删除操作变成相同的最小步骤数。

输入: "sea", "eat"
输出: 2
解释: 第一步将"sea"变为"ea",第二步将"eat"变为"ea"

解析:

​ 本题是最长公共子序列的一种变种题,可以直接求出最长公共子序列后用较长一个字符串的长度减去最长公共子序列的长度。

class Solution {
public:
    int minDistance(string word1, string word2) {
        int m = word1.length(), n = word2.length();
        vector<vector<int>> dp(m+1,vector<int>(n+1));
        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] + 1;
                }else{
                    dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        int LCS = dp[m][n];
        return m+n-2*LCS;
    }
};
上次更新: 2023/11/19, 12:55:48
03分割型动态规划
05背包问题

← 03分割型动态规划 05背包问题→

Theme by Vdoing | Copyright © 2023-2024 Wang Qinghuan | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式