[300][中等][贪心][二分][动态规划][树状数组] 最长上升子序列

题目描述

300. 最长上升子序列

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例:

输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。

说明:

  • 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。

  • 你算法的时间复杂度应该为 O(n2) 。

进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

解题思路

动态规划

定义dp[i]是前i个元素中, 以第i个元素为结尾的最长上升子序列的长度, 即nums[i]必须被选取.

要求新位置的状态值, 就需要遍历这个位置之前所有的dp[i], 看上升子序列能否拼接上当前的数字, 因此状态转移方程为:

dp[i]=max(dp[j])+1,其中0j<inum[j]<num[i]dp[i] = \text{max}(dp[j]) + 1, \text{其中} \, 0 \leq j < i \, \text{且} \, \textit{num}[j]<\textit{num}[i]

最后遍历整个dp, 找到最大值.

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        n = len(nums)
        if n == 0:
            return 0

        dp = [1]
        for i in range(1, n):
            max_seq = 1
            for j in range(i):
                if nums[i] > nums[j]:
                    max_seq = max(max_seq, dp[j] + 1)
            dp.append(max_seq)
        return max(dp)

贪心 + 二分

一步一步推导出官方最优解法,详细图解

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        n = len(nums)
        if n == 0:
            return 0

        mins = []
        for num in nums:
            index = bisect.bisect_left(mins, num)
            if index == len(mins):
                mins.append(num)
            else:
                mins[index] = num
        return len(mins)

树状数组

首先将原数组nums去重排序, 得到树状数组C的长度和每个位置对应的数值. 树状数组中记录的值为以当前区间内的任一数字结尾的最长子序列的长度, 因此更新需要使用max, 而不是加和.

从左到右遍历数组, 对于数值num, 找到其在树状数组中的位置index. 首先我们遍历这个位置之前所有区间的树状数组的值, 因为num可以拼接到其之前的任意一个数之后, 组成上升序列. 找到其中的最大值, 再加上num本身的长度1, 就是num这个数在原数组对应位置的最大上升序列长度了, 记为current_max.

找到这个结果之后, 要更新树状数组, 其父节点所代表的区间, 对应的树状数组的值, 也要判断下是否要更新. 一路向上更新即可.

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        n = len(nums)
        if n == 0:
            return 0

        A = list(set(nums))
        A.sort()
        A = [0] + A
        mapping = {num: i for i, num in enumerate(A)}
        C = [0] * len(A)

        final_max = 0
        for num in nums:
            index = mapping[num] - 1  # 严格递增, 从第一个小于此数的位置找起
            current_max = 0
            while index > 0:
                current_max = max(current_max, C[index])
                index -= index & (-index)
            final_max = max(final_max, current_max + 1)

            # 修改
            index = mapping[num]
            while index < len(A):
                C[index] = max(C[index], current_max + 1)
                index += index & (-index)

        return final_max

最后更新于