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

🔥小叶-duck个人主页

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

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


目录

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

题目链接:

题目描述:

题目示例:

解法(动态规划):

算法思路:

C++算法代码:

算法总结及流程解析:

30.最长数对链

题目链接:

题目描述:

题目示例:

解法(动态规划):

算法思路:

C++算法代码:

算法总结及流程解析:

31.最长定差子序列

题目链接:

题目描述:

题目示例:

解法(动态规划):

算法思路:

C++算法代码:

算法总结及流程解析:

结束语


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

题目链接:

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

题目描述:

题目示例:

解法(动态规划):

算法思路:

1.状态表示:

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

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

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

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

2.状态转移方程:

求个数之前,我们得先知道长度,因此先看leni

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

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

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

综上所述,对于几eni,我们可以得到状态转移方程为:
leni =max(lenj + 1,leni),其中0 <=j < i,并且numsj < numsi

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

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

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

综上所述,对于counti,我们可以得到状态转移方程为:
counti += countj,其中@ <=j< i,并且numsj <numsi && dpj + 1 == dpi

3.初始化:

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

对于counti,如果全部初始化为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.最长递增子序列。因此我们解决问题的方向,应该在「最长递增子序

列」这个模型上。

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

1.状态表示:

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

2.状态转移方程:

对于dpi,遍历所有0,i-1区间内数对用j表示下标,找出所有满足pairsj1<pairsi0的j。找出里面最大的dpj,然后加上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.状态表示:

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

2.状态转移方程:

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

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

3.初始化:

刚开始的时候,需要把第一个元素放进哈希表中,hasharr\[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)查找。**希望大家能有所收获!

相关推荐
xieliyu.1 小时前
Java算法精讲:双指针(三)
java·开发语言·算法
一条小锦吕*2 小时前
基于Spring Boot + 数据可视化 + 协同过滤算法的推荐系统设计与实现(源码+论文+部署全讲解)
spring boot·算法·信息可视化
如竟没有火炬3 小时前
最大矩阵——单调栈
数据结构·python·线性代数·算法·leetcode·矩阵
8Qi84 小时前
LeetCode 1143 & 718:最长公共子序列 / 最长重复子数组
算法·leetcode·职场和发展·动态规划
绿算技术4 小时前
万卡推理集群存储选型分析:从核心架构到应用视角
大数据·科技·算法·架构
Qt程序员5 小时前
Linux RCU 原理与应用
linux·c++·内核·linux内核·rcu
想吃火锅10055 小时前
【leetcode】1.两数之和js版
javascript·算法·leetcode
qeen875 小时前
【C++】类与对象之类的默认成员函数(二)
android·c语言·开发语言·c++·笔记·学习
net3m336 小时前
一阶软件低通滤波器算法
人工智能·算法