📜  计数增加子序列的数量:O(NlogN)(1)

📅  最后修改于: 2023-12-03 15:27:57.231000             🧑  作者: Mango

计数增加子序列的数量:O(NlogN)
简介

增加子序列是指序列中的数出现的先后顺序不变,但是位置不一定相邻,且数值递增的序列。计数增加子序列的数量是一道常见的算法问题,它的时间复杂度为O(NlogN)。

解题思路

本题的解题思路主要是基于动态规划的思想。假设dp[i]表示以第i个数结尾的最长递增子序列的长度,那么状态转移方程就是dp[i] = max(dp[j]+1),其中 j < i 且 nums[j] < nums[i]。这个方程表明,如果第i个数要与前面的某个数构成递增子序列,它必须在这个子序列的结尾位置,而且前面的这个子序列也必须是以递增的方式构成的。

但是,仅仅求解最长递增子序列的长度依然不足以解决这个问题,因为有可能存在长度相同但是位置不同的递增子序列。为了避免重复计算,我们可以在求解dp[i]的过程中,将所有长度为dp[i]-1的递增子序列的结尾位置都记录下来,然后将当前位置i插入到这些子序列后面,从而得到一些新的递增子序列。这样做的时间复杂度为O(N^2),效率较低,无法通过本题。

考虑使用一种更为高效的算法,即利用二分查找的方法减少时间复杂度。我们定义一个数组d,d[k]表示长度为k的递增子序列的结尾元素的最小值。假设当前处理到第i个数时,它属于某个长度为len的递增子序列,那么可以得出结论:只有在d[len+1]时才需要更新d数组。因为如果i的值大于长度为len的任何一个递增子序列的结尾值,那么它可以插入该子序列后面,形成一个长度更长的递增子序列。如果i的值小于长度为len的任何一个递增子序列的结尾值,那么它可以替换掉该子序列的结尾值,因为替换后结果一定不劣。通过二分查找可以快速定位i在d数组中的位置,并进行相应的更新。最终得到的d数组长度就是所求的最长递增子序列长度。

最后,我们可以使用一个单调递增的栈,从右到左依次遍历原始序列,将每个元素处于何种长度的递增子序列对应的方案数都统计出来,最终得到的就是所有递增子序列的方案数之和。

代码示例
def count_increasing_subsequences(nums):
    n = len(nums)
    dp = [1] * n
    d = [float('inf')] * (n+1)
    d[1] = nums[0]
    cnt = [0] * (n+1)
    cnt[1] = 1
    ans = 0
    for i in range(1, n):
        left, right = 1, i+1
        while left < right:
            mid = (left + right) // 2
            if d[mid] < nums[i]:
                left = mid + 1
            else:
                right = mid
        len = left
        dp[i] = len
        d[len] = min(d[len], nums[i])
        cnt[len] += cnt[len-1]
        ans += cnt[len-1]
    return ans
总结

本题的解题思路比较复杂,需要对动态规划、二分查找等算法有一定的了解。但只要掌握了这些算法,题目并不难求解,时间复杂度也能保证在O(NlogN)之内。