在问 · 算法题解

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

中等动态规划动态规划(长度 + 个数)力扣原题 ↗

题目描述

输入一个整数数组 nums,数组未排序。

子序列是从数组中删去若干元素(也可以不删)后、其余元素保持原顺序得到的序列。若子序列中后一个元素总是严格大于前一个,就称它是递增的。

在所有递增子序列中,找出长度最大的那些,返回它们的个数。由不同下标集合组成的子序列分别计数。

示例 1
输入:nums = [1,3,5,4,7]
输出:2

解释:最长长度为 4,有 [1,3,4,7] 和 [1,3,5,7] 两个。

示例 2
输入:nums = [2,2,2,2,2]
输出:5

解释:元素全相等,最长递增子序列长度为 1,共有 5 个。

数据范围

  • 1 <= nums.length <= 2000
  • -10^6 <= nums[i] <= 10^6

解题思路:动态规划(长度 + 个数)

在最长递增子序列 DP 的基础上多记一个计数。length[i] 表示以 nums[i] 结尾的最长长度,cnt[i] 表示达到这个长度的方案数。枚举 j < i 且 nums[j] < nums[i]:如果 length[j] + 1 更长,就更新长度并把计数重置为 cnt[j];如果等长,就把 cnt[j] 累加到 cnt[i]。最后把 length 等于全局最大值的那些位置的 cnt 加起来。

为什么正确

处理完第 i 位后,length[i] 与 cnt[i] 对前缀 nums[0..i] 中以 i 结尾的递增子序列是精确的。

复杂度分析

时间 O(n^2),空间 O(n)。

易错点

最后不能只取 cnt[n-1],而是要累加所有达到最大长度的结尾位置。长度更新时要把计数重置为 cnt[j],不能累加。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class Solution:
    def findNumberOfLIS(self, nums: List[int]) -> int:
        n = len(nums)
        length = [1] * n
        cnt = [1] * n
        for i in range(n):
            for j in range(i):
                if nums[j] < nums[i]:
                    if length[j] + 1 > length[i]:
                        length[i] = length[j] + 1
                        cnt[i] = cnt[j]
                    elif length[j] + 1 == length[i]:
                        cnt[i] += cnt[j]
        best = max(length)
        return sum(c for l, c in zip(length, cnt) if l == best)

载入编辑器,自己跑一遍 →

其他解法

动态规划对每个位置枚举所有前驱,直观但 O(n²);贪心解法按长度分层并保持层内有序,用二分和前缀和一次求出可接的方案数。

解法 2:贪心 + 前缀和 + 二分查找 时间 O(n log n),空间 O(n)

在求最长递增子序列长度的贪心解法上扩展。d[i] 不再只存长度为 i + 1 的最小结尾,而是按出现顺序存下所有曾经成为结尾的值,这些值单调不增。cnt[i] 是 d[i] 对应方案数的前缀和。处理 v 时,先用二分找到第一个末尾不小于 v 的 d[i],确定 v 结尾的最长长度。再在 d[i − 1] 中二分找到第一个小于 v 的位置 k,从 k 到末尾的方案都能接上 v,用前缀和作差得到个数。最后一层前缀和的末项就是答案。

两次二分代替内层循环,降到 O(n log n)。代价是结构复杂,需要同时理解贪心求 LIS 和 d[i] 内部的单调性。

class Solution:
    def findNumberOfLIS(self, nums: List[int]) -> int:
        d, cnt = [], []
        for v in nums:
            lo, hi = 0, len(d)
            while lo < hi:
                mid = (lo + hi) // 2
                if d[mid][-1] >= v:
                    hi = mid
                else:
                    lo = mid + 1
            i = lo
            c = 1
            if i > 0:
                prev = d[i - 1]
                lo, hi = 0, len(prev)
                while lo < hi:
                    mid = (lo + hi) // 2
                    if prev[mid] < v:
                        hi = mid
                    else:
                        lo = mid + 1
                c = cnt[i - 1][-1] - cnt[i - 1][lo]
            if i == len(d):
                d.append([v])
                cnt.append([0, c])
            else:
                d[i].append(v)
                cnt[i].append(cnt[i][-1] + c)
        return cnt[-1][-1]

动态规划 专题的其他题目

查看 动态规划 全部题目 →