《算法题讲解指南:动态规划算法--子序列问题》--29.最长递增子序列的个数,30.最长数对链,31.最长定差子序列

🔥小叶-duck个人主页

❄️个人专栏《Data-Structure-Learning》《C++入门到进阶&自我学习过程记录》
《算法题讲解指南》--优选算法
《算法题讲解指南》--递归、搜索与回溯算法
《算法题讲解指南》--动态规划算法

未择之路,不须回头
已择之路,纵是荆棘遍野,亦作花海遨游


目录

29.最长递增子序列的个数

题目链接:

题目描述:

题目示例:

解法(动态规划):

算法思路:

C++算法代码:

算法总结及流程解析:

30.最长数对链

题目链接:

题目描述:

题目示例:

解法(动态规划):

算法思路:

C++算法代码:

算法总结及流程解析:

31.最长定差子序列

题目链接:

题目描述:

题目示例:

解法(动态规划):

算法思路:

C++算法代码:

算法总结及流程解析:

结束语


29.最长递增子序列的个数

题目链接:

673. 最长递增子序列的个数 - 力扣(LeetCode)

题目描述:

题目示例:

解法(动态规划):

算法思路:

1.状态表示:

先尝试定义一个状态:以为结尾的最长递增子序列的「个数」。那么问题就来了,我都不知道以i为结尾的最长递增子序列的「长度」是多少,我怎么知道最长递增子序列的个数呢?

因此,我们解决这个问题需要两个状态,一个是「长度」,一个是「个数」:

len[i]表示:以i为结尾的最长递增子序列的长度;

count[i]表示:以i为结尾的最长递增子序列的个数。

2.状态转移方程:

求个数之前,我们得先知道长度,因此先看len[i]:

i.在求i 结尾的最长递增序列的长度时,我们已经知道 [0,i-1] 区间上的 len[j] 信息,用 j 表示[0,i-1]区间上的下标;

ii.我们需要的是递增序列,因此[0,i-1]区间上的 nums[j]只要能和 nums[i]构成上升序列,那么就可以更新dp[i]的值,此时最长长度为dp[j]+1;

iii.我们要的是[0,i-1]区间上所有情况下的最大值。

综上所述,对于几en[i],我们可以得到状态转移方程为:
len[i] =max(len[j] + 1,len[i]),其中0 <=j < i,并且nums[j] < nums[i] 。

在知道每一个位置结尾的最长递增子序列的长度时,我们来看看能否得到count[i]:

i.我们此时已经知道len[i]的信息,还知道[,i-1]区间上的count[j]信息,用j表示[0,i-1]区间上的下标;

ii.我们可以再遍历一遍[0,i1]区间上的所有元素,只要能够构成上升序列,并且上升序列的长度等于dp[i],那么我们就把count[i]加上count[j]的值。这样循环一遍之后,count[i]存的就是我们想要的值。

综上所述,对于count[i],我们可以得到状态转移方程为:
count[i] += count[j],其中@ <=j< i,并且nums[j] <nums[i] && dp[j] + 1 == dp[i]。

3.初始化:

对于len[i],所有元素自己就能构成一个上升序列,直接全部初始化为1;

对于count[i],如果全部初始化为1,在累加的时候可能会把「不是最大长度的情况」累加进去,因此,我们可以先初始化为0,然后在累加的时候判断一下即可。具体操作情况看代码~

4.填表顺序:

毫无疑问是「从左往右」。

5.返回值:

用manLen 表示最终的最长递增子序列的长度。

根据题目要求,我们应该返回所有长度等于maxLen的子序列的个数。

C++算法代码:

cpp 复制代码
class Solution {
public:
    int findNumberOfLIS(vector<int>& nums) 
    {
        int n = nums.size();
        vector<int> len_dp(n, 1);
        vector<int> count_dp(n, 1);
        int len = 1;
        int count = 1;

        for(int i = 1; i < n; i++)
        {
            for(int j = i - 1; j >= 0; j--)
            {
                if(nums[i] > nums[j])
                {
                    // len_dp[i] = max(len_dp[i], len_dp[j] + 1);
                    if(len_dp[i] < len_dp[j] + 1)
                    {
                        len_dp[i] = len_dp[j] + 1;
                        count_dp[i] = count_dp[j];
                    }
                    else
                    {
                        if(len_dp[i] == len_dp[j] + 1)
                        {
                            count_dp[i] += count_dp[j];
                        }
                    }
                }
            }
            if(len < len_dp[i])
            {
                len = len_dp[i];
                count = count_dp[i];
            }
            else
            {
                if(len == len_dp[i])
                {
                    count += count_dp[i];
                }
            }
        }    
        // int len = 0;
        // int count = 0;
        // for(int i = 0; i < n; i++)
        // {
        //     if(len < len_dp[i])
        //     {
        //         len = len_dp[i];
        //         count = count_dp[i];
        //     }
        //     else
        //     {
        //         if(len == len_dp[i])
        //         {
        //             count += count_dp[i];
        //         }
        //     }
        // }
        return count;
    }
};

算法总结及流程解析:

30.最长数对链

题目链接:

646. 最长数对链 - 力扣(LeetCode)

题目描述:

题目示例:

解法(动态规划):

算法思路:

这道题目让我们在数对数组中挑选出来一些数对,组成一个呈现上升形态的最长的数对链。像不像我们整数数组中挑选一些数,让这些数组成一个最长的上升序列?因此,我们可以把问题转化成我们学过的一个模型:300.最长递增子序列。因此我们解决问题的方向,应该在「最长递增子序

列」这个模型上。

不过,与整形数组有所区别。在用动态规划结局问题之前,应该先把数组排个序。因为我们在计算dp[i]的时候,要知道所有左区间比pairs[i]的左区间小的链对。排完序之后,只用「往前遍历一遍」即可。

1.状态表示:

dp[i]表示以i 位置的数对为结尾时,最长数对链的长度。

2.状态转移方程:

对于dp[i],遍历所有[0,i-1]区间内数对用j表示下标,找出所有满足pairs[j][1]<pairs[i][0]的j。找出里面最大的dp[j],然后加上1 ,就是以i位置为结尾的最长数对链。

3.初始化:

刚开始的时候,全部初始化为1。

4.填表顺序:

根据「状态转移方程」,填表顺序应该是「从左往右」。

5.返回值:

根据「状态表示」,返回整个dp表中的最大值。

C++算法代码:

cpp 复制代码
class Solution {
public:
    int findLongestChain(vector<vector<int>>& pairs) 
    {
        sort(pairs.begin(), pairs.end());
        int n = pairs.size();
        vector<int> dp(n, 1);
        int ret = 1;

        for(int i = 1; i < n; i++)
        {
            for(int j = i - 1; j >= 0; j--)
            {
                if(pairs[j][1] < pairs[i][0])
                {
                    dp[i] = max(dp[i], dp[j] + 1);
                }
            }
            ret = max(ret, dp[i]);
        }
        // int ret = 0;
        // for(int i = 0; i < n; i++)
        // {
        //     ret = max(ret, dp[i]);
        // }
        return ret;
    }
};

算法总结及流程解析:

31.最长定差子序列

题目链接:

1218. 最长定差子序列 - 力扣(LeetCode)

题目描述:

题目示例:

解法(动态规划):

算法思路:

这道题和300.最长递增子序列有一些相似,但仔细读题就会发现,本题的 arr.lenght 高达10^5 ,使用 O(N^2) 的 lcs 模型一定会超时。

那么,它有什么信息是300.最长递增子序列的呢? 是定差。之前,我们只知道要递增,不知道前一个数应当是多少 ,就会导致可能前面的值忽大忽小 ,所以我们需要把前面所有情况遍历一遍 才能找到最大值;现在我们可以计算出前一个数具体是多少了,就可以用数值来定义dp数组的值,并形成状态转移。这样,就把已有信息有效地利用了起来。

1.状态表示:

dp[i]表示:以i位置的元素为结尾所有的子序列中,最长的等差子序列的长度。

2.状态转移方程:

对于dp[i],上一个定差子序列的取值定为arr[i]-difference 。只要找到以上一个数字为结尾的定差子序列长度的dp[arr[i]-difference],然后加上1,就是以i为结尾的定差子序列的长度。

因此,这里可以选择使用哈希表做优化。我们可以把「元素,dp[j]」绑定,放进哈希表中。甚至不用创建 dp 数组,直接在哈希表中做动态规划。

3.初始化:

刚开始的时候,需要把第一个元素放进哈希表中,hash[arr[0]]=1 。

4.填表顺序:

根据「状态转移方程」,填表顺序应该是「从左往右」。

5.返回值:

根据「状态表示」,返回整个 dp 表中的最大值。

C++算法代码:

cpp 复制代码
class Solution {
public:
    int longestSubsequence(vector<int>& arr, int difference) 
    {
        //创建一个哈希表,利用哈希表实现动态规划
        unordered_map<int, int> hash; //arr[i] - dp[i]
        hash[arr[0]] = 1; //初始化
        int ret = 1;

        for(int i = 1; i < arr.size(); i++)
        {
            // if(hash[arr[i] - difference])
            // {
            //     hash[arr[i]] = hash[arr[i] - difference] + 1;
            // }
            // else
            // {
            //     hash[arr[i]] = 1;
            // }
            hash[arr[i]] = hash[arr[i] - difference] + 1;
            //如果arr[i] - difference在前面不存在,则hash映射的值为0
            //加上1也正好符合要求,不需要额外判断

            ret = max(ret, hash[arr[i]]);
        }
        // int ret = 0;
        // for(int i = 0; i < arr.size(); i++)
        // {
        //     ret = max(ret, hash[arr[i]]);
        // }
        return ret;
    }
};

算法总结及流程解析:

结束语

到此,29.最长递增子序列的个数,30.最长数对链,31.最长定差子序列 这三道算法题就讲解完了。**最长递增子序列个数:通过维护长度和个数两个状态数组,统计满足条件的序列数量;.最长数对链:转化为LIS问题并排序预处理;最长定差子序列:利用哈希表优化查找过程。三题均采用动态规划思想,通过定义状态、转移方程、初始化和填表顺序解决问题,其中第一题需同时跟踪长度和数量信息,第三题利用哈希表实现O(1)查找。**希望大家能有所收获!

相关推荐
无限进步_5 分钟前
【C++】巧用静态变量与构造函数:一种非常规的求和实现
开发语言·c++·git·算法·leetcode·github·visual studio
小超超爱学习993722 分钟前
大数乘法,超级简单模板
开发语言·c++·算法
Ricardo-Yang36 分钟前
SCNP语义分割边缘logits策略
数据结构·人工智能·python·深度学习·算法
凌波粒37 分钟前
LeetCode--344.反转字符串(字符串/双指针法)
算法·leetcode·职场和发展
啊哦呃咦唔鱼1 小时前
LeetCode hot100-543 二叉树的直径
算法·leetcode·职场和发展
sinat_286945191 小时前
harness engineering
人工智能·算法·chatgpt
少许极端2 小时前
算法奇妙屋(四十三)-贪心算法学习之路10
学习·算法·贪心算法
xyx-3v2 小时前
qt创建新工程
开发语言·c++·qt
算法鑫探2 小时前
10个数下标排序:最大值、最小值与平均值(下)
c语言·数据结构·算法·排序算法·新人首发
样例过了就是过了2 小时前
LeetCode热题100 爬楼梯
c++·算法·leetcode·动态规划