Leetcode 300:最长上升子序列(最详细的解法!!!)

版权声明:本文为博主原创文章,未经博主允许不得转载。有事联系:[email protected] https://blog.csdn.net/qq_17550379/article/details/82871892

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

示例:

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

说明:

  • 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
  • 你算法的时间复杂度应该为 O(n2) 。

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

解题思路

首先想到的解法就是找出所有的子序列,然后对每个子序列进行判断,如果是上升子序列我们就记录高度,最后我们取所有高度中的最大值即可。关于所有子集的问题,可以看这两篇Leetcode 78:子集(最详细的解法!!!)Leetcode 90:子集 II(最详细的解法!!!) 。但是我们这里没有使用上述的代码实现,我使用了内建函数itertools.combinations

import itertools
class Solution:
    def lengthOfLIS(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        all_list = list()
        len_nums = len(nums)
        for i in range(1, len_nums + 1):
            all_list.extend([list(per) for per in itertools.combinations(nums, i)])
        
        result, flag = 0, 1
        for per_list in all_list:
            for i, _ in enumerate(per_list[:-1]):
                if per_list[i] > per_list[i + 1]:
                    flag = 0
                    continue
            if flag:
                result = max(result, len(per_list))

            flag = 1

        return result

但是这个算法显然是不合理的,时间复杂度太高了。有没有更快的?实际上这是一个动态规划可以解决的问题。我们定义这样的一个函数_lehgthOfLIS(indx)index表示nums中的位置,而函数整体表示以nums[index]数字结尾的最长子序列的长度。

我们index位置的函数值,由index之前的max(f(j)) (j < index)决定,并且nums[index] > nums[j]。为什么呢?因为一旦之前序列的最后一个值nums[j]大于现在即将变为整个序列最后一个元素的nums[index],整个序列不再保持上升趋势。所以我们可以通过下列方程表示这个过程

  • f(i) = 1 + max(f(j) if nums[i] > nums[j]) (j < i)

通过整个方程,可以快速的写出代码

class Solution:
    def lengthOfLIS(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        if not nums:
            return 0

        mem = [1 for _ in nums]
        len_nums = len(nums)
        result = 1
        for i in range(1, len_nums):
            for j in range(i):
                if nums[j] < nums[i]:
                    mem[i] = max(mem[i], 1 + mem[j])
                    
            result = max(result, mem[i])

        return result

这是我们大多数人可以想到的解法,实际上这不是最快的算法,这个算法的时间复杂度是O(n^2),我们还有比这个更快的算法。

我们建立一个临时数组tmp,将nums[0]插入其中,然后遍历nums[1:]

  • 如果遍历到的元素val <= tmp[0],我们更新tmp[0]=val
  • 如果tmp[0] < val <= tmp[-1],我们通过二分搜索法找到第一个>=val的元素位置,然后用val替换掉它
  • 如果tmp[-1] < val,我们更新tmp.append(val)

一题目中的例子为例,我们建立一个tmp=[10],然后我们发现10 < 9,所以我们

tmp: [9]

然后我们考虑9 < 2,所以

tmp: [2]

然后我们考虑2 < 5,所以

tmp: [2 5]

接着往后5 > 3 && 2 < 5,所以

tmp: [2 3]

3 < 7,所以

tmp: [2 3 7]

接着7 < 101,所以

tmp: [2 3 7 101]

然后2 < 18 < 101,所以

tmp: [2 3 7 18]

基于这个思想,我们可以写出下面的代码

class Solution:
    def lengthOfLIS(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        if not nums:
            return 0

        nums_len = len(nums)
        tmp = [nums[0]]
        for i in range(1, nums_len):
            if nums[i] < tmp[0]:
                tmp[0] = nums[i]
            elif nums[i] > tmp[-1]:
                tmp.append(nums[i])
            else:
                low, upper = 0, len(tmp)
                while low < upper:
                    mid = (upper - low)//2 + low
                    if nums[i] > tmp[mid]:
                        low = mid + 1
                    else:
                        upper = mid
                
                tmp[upper] = nums[i]

        return len(tmp)

实际上上面的写法还可以简化为

class Solution:
    def lengthOfLIS(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        mem = list()
        len_nums = len(nums)
        for i in range(len_nums):
            low, upper = 0, len(mem)
            while low < upper:
                mid = (upper - low)//2 + low
                if mem[mid] < nums[i]:
                    low = mid + 1
                else:
                    upper = mid

            if upper == len(mem):
                mem.append(nums[i])
            else:
                mem[upper] = nums[i]
        
        return len(mem)

reference:

https://leetcode.com/problems/longest-increasing-subsequence/discuss/152065/Python-explain-the-O(nlogn)-solution-step-by-step

我将该问题的其他语言版本添加到了我的GitHub Leetcode

如有问题,希望大家指出!!!

猜你喜欢

转载自blog.csdn.net/qq_17550379/article/details/82871892