给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [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:
我将该问题的其他语言版本添加到了我的GitHub Leetcode
如有问题,希望大家指出!!!