给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:
进阶: 你能将算法的时间复杂度降低到 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
但是这个算法显然是不合理的,时间复杂度太高了。有没有更快的?实际上这是一个动态规划可以解决的问题。我们定义这样的一个函数 f ( x ) f(x) f(x)表示以 n u m s [ x ] nums[x] nums[x]数字结尾的最长子序列的长度,其中 x x x表示 n u m s nums nums中的位置。
x x x位置的函数值由 x x x之前的 m a x ( f ( j ) ) j < x a n d n u m s [ x ] > n u m s [ j ] max(f(j))\ j < x\ and \ nums[x]>nums[j] max(f(j)) j<x and nums[x]>nums[j]决定。所以
通过整个方程,可以快速的写出代码
class Solution:
def lengthOfLIS(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
if not nums:
return 0
len_nums = len(nums)
mem = [1]*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]
基于这个思想,我们可以写出下面的代码
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)
实际上上面的写法还可以简化为
from bisect import bisect_left
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
mem = list()
n = len(nums)
for i in range(n):
index = bisect_left(mem, nums[i])
if len(mem) == index:
mem.append(nums[i])
else:
mem[index] = 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
如有问题,希望大家指出!!!