【LeetCode】最长递增子序列的个数 [M](动态规划)

该问题通过动态规划和二分查找算法解决,给定一个未排序的整数数组,返回最长递增子序列的个数。代码中定义了一个二维数据结构dp,用于存储以每个数字结尾的递增子序列信息,并使用二分查找优化查找效率。最终返回dp中最大长度递增子序列的个数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

一、题目

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

注意 这个数列必须是 严格 递增的。

示例 1:

输入: [1,3,5,4,7]
输出: 2
解释: 有两个最长递增子序列,分别是 [1, 3, 4, 7] 和[1, 3, 5, 7]。

示例 2:​​​​​​​

输入: [2,2,2,2,2]
输出: 5
解释: 最长递增子序列的长度是1,并且存在5个子序列的长度为1,因此输出5。

提示:

  • 1 <= nums.length <= 2000
  • -106 <= nums[i] <= 106

二、代码

class Solution {
    // 时间复杂度O(N*logN)
    public int findNumberOfLIS(int[] nums) {
        // 过滤无效参数
        if (nums == null || nums.length == 0) {
            return 0;
        }
        // 设置dp,里面存储了所有长度的递增子序列中以每一个nums[i]为结尾的子序列个数分别有多少个
        // dp.get(i):存储长度为i的递增子序列信息
        // dp.get(i).(key, value):表示以大于等于key结尾的长度为i的递增子序列的个数为value个
        ArrayList<TreeMap<Integer, Integer>> dp = new ArrayList<>();
        // 遍历nums
        for (int num : nums) {
            // 记录以num结尾的递增子序列的最大长度
            int len = 0;
            // 记录以num结尾的最大递增子序列的个数
            int cnt = 0;

            // 1、找到以num结尾的最长递增子序列的长度
            // 用二分法找到dp中找到一个长度最小的无法和num构成递增子序列的长度,即找到一种长度的最小结尾数是大于等于num的,并且这个长度是满足这个条件中最小的长度
            len = search(dp, num);

            // 2、计算以num结尾的最长递增子序列的个数
            // 如果len == 0,说明此时num比所有的数都小,无法和他们组成递增子序列,只能自己组成一个子序列,个数为1个
            if (len == 0) {
                cnt = 1;
            // num可以和以前的子序列组成更长的递增子序列    
            } else {
                // 获取这个可以和num组成递增子序列的最长递增子序列信息
                TreeMap<Integer, Integer> treeMap = dp.get(len - 1);
                // 以num结尾的最长递增子序列个数就是长度为len-1的递增子序列中所有结尾数小于num的个数总和
                // treeMap.firstEntry().getValue():长度为len-1的递增子序列总数
                // treeMap.ceilingEntry(num).getValue():长度为len-1的递增子序列中结尾值大于num的子序列总数
                // 上面两个数相减得到的就是长度为len-1的递增子序列中所有结尾数小于num的个数总和
                cnt = treeMap.firstEntry().getValue() - (treeMap.ceilingEntry(num) == null ? 0 : treeMap.ceilingEntry(num).getValue());
            }

            // 3、将计算得到的len和cnt加入到dp中
            // 如果len == dp.size(),说明以num结尾的最长递增子序列已经推高了当前找到的最长递增子序列长度,需要新开一个TreeMap来存储
            if (len == dp.size()) {
                dp.add(new TreeMap<Integer, Integer>());
                // 将以num结尾的最长递增子序列数据加入到dp中
                dp.get(len).put(num, cnt);
            // 服用以前已有的TreeMap来存储以num结尾的最长递增子序列数据
            } else {
                // 这里要注意,len长度的递增子序列中,此时num一定是最小结尾
                // 因为前面通过二分法找到的len长度是长度最小的无法和num构成递增子序列的长度,即len长度的递增子序列原本的最小结尾数是大于num的,
                // 所以num的加入一定会作为len长度的递增子序列中新的最小结尾数
                // 所以在计算长度等于len的递增子序列中结尾数大于等于num的个数时,就直接用dp.get(len).firstEntry().getValue() + cnt
                // dp.get(len).firstEntry().getValue():原本就在TreeMap中结尾数大于等于num的总数,它也是原来的长度为len的递增子序列的总个数
                dp.get(len).put(num, dp.get(len).firstEntry().getValue() + cnt);
            }
        }
        // 直接返回dp中最大长度的递增子序列总个数即可
        // 最大长度:dp.size() - 1
        // 递增子序列总个数:dp.get(dp.size() - 1).firstEntry().getValue()
        return dp.get(dp.size() - 1).firstEntry().getValue();
    }

    // 二分查找,返回TreeMap.firstEntry().getKey()>=num最左的位置
    public int search(ArrayList<TreeMap<Integer, Integer>> dp, int num) {
        int l = 0;
        int r = dp.size() - 1;
        // 如果最后返回dp.size()就说明此时dp中没有比num大的
        int ans = dp.size();
        while (l <= r) {
            int m = (l + r) >> 1;
            // TreeMap.firstEntry()就是当前这个长度的递增子序列中结尾数最小的子序列
            if (dp.get(m).firstEntry().getKey() >= num) {
                // 记录大于等于num的答案
                ans = m;
                r = m - 1;
            } else {
                // 如果一直执行这个分支就会返回0,说明num比当前所有的数都小
                l = m + 1;
            }
        }
        return ans;
    }
   
}

三、解题思路 

假设遍历到了x这个数,先通过二分法去找x能组成的最长长度的递增子序列是多少,假设它最长能组成Y长度,这是因为Y-1长度的最小结尾数小于x,但是Y长度的最小结尾数大于x,所以x最多就是和Y-1长度的子序列组成递增子序列。

然后利用有序表找到Y-1长度中结尾数大于x的最小值是多少,假设是z,找这个位置记录的个数,假设为c个,假设Y-1长度的子序列总数为a(即这个长度记录中大于等于结尾值最小的那个个数),然后用Y-1长度递增子序列的总数量a减去c,得到的就是Y-1长度中结尾小于x的个数一共有a-c个,也就是能组成的长度为Y以x结尾的递增子序列的个数为a-c个。

然后长度为Y的记录中大于x最近的值是k,k的记录是d个,表示长度为Y结尾大于等于k的子序列有d个,那么记录在大于等于x的个数就是a-c+d个。

至此就完成了整个流程的处理,当把所有数都遍历完一遍后,记录的结构中最大长度的总子序列个数(即记录的大于等于最小结位值位置的个数)就是最后的最长递增子序列个数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值