LeetCode:300. 最长上升子序列(python)
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:
- 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
- 你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?
思路1:动态规划
- 确定状态和选择
- 状态:序列
0~i
,最终状态为序列0~n-1
,n
为序列的长度,则用dp[i]
记录当前位置i
处的最长上升子序列的长度。 - 选择:计算
dp[i]
,若nums[i]>nums[j]
,0<=j<i
,则判断dp[j]+1
大于dp[i]
,若是则更新dp[i]
。因此有i-1
种选择,需要通过两次遍历求解,时间复杂度为 O ( n 2 ) O(n^2) O(n2)。
- 状态:序列
附代码1(Python3):
class Solution:
def lengthOfLIS(self, nums):
if not nums:
return 0
n, res = len(nums), 1 # 序列长度和初始化最长上升子序列长度
dp = [1]*n # 初始化 dp
for i in range(1, n):
for j in range(i):
if nums[i] > nums[j]:
dp[i] = max(dp[i], dp[j]+1) # 更新 dp
res = max(res, dp[i]) # 更新最长上升子序列长度
return res
test = Solution()
nums = [10,9,2,5,3,7,101,18]
test.lengthOfLIS(nums)
4
思路2:二分法求解
- 按顺序将序列
0~n-1
的值插入到数组的尾端,数组要求从大到小排序,若无法插入,则添加一个包含该值的新数组;遍历结束后,数组的个数即为最长上升子序列的长度 - 通过二分法查找合适的插入位置,比较当前值与每个数组的尾端的值大小
- 整体时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
附代码2(Python3):
class Solution:
def lengthOfLIS(self, nums):
if not nums:
return 0
top = [] # 整体数组
for i in range(len(nums)):
# 二分查找插入数组
left, right = 0, len(top)-1 # 左右边界索引
while left <= right:
mid = (left+right)//2 # 左中位数
if top[mid][-1] < nums[i]:
left = mid+1
else:
right = mid-1
# 若 left 等于数组的个数,则需要添加新数组;否则,在 left 数组尾部添加新值
if left == len(top):
top.append([nums[i]])
else:
top[left].append(nums[i])
return len(top)
test = Solution()
nums = [10,9,2,5,3,7,101,18]
test.lengthOfLIS(nums)
4
思路4:二分法求解优化
- 二分法查找数组时只需要查看数组的最后一个值,并不需要记录数组的所有值,只需要将最后一个值记录即可,因此用一维数组代替整体数组(二维数组)。
附代码4(Python):
class Solution:
def lengthOfLIS(self, nums):
if not nums:
return 0
top = []
for i in range(len(nums)):
# 二分查找
left, right = 0, len(top)-1 # 左右边界索引
while left <= right:
mid = (left+right)//2 # 左中位数
if top[mid] < nums[i]:
left = mid+1
else:
right = mid-1
# 若 left 等于数组长度,则需要添加新值;否则,在 left 位置的值覆盖为新值
if left == len(top):
top.append(nums[i])
else:
top[left] = nums[i]
return len(top)
test = Solution()
nums = [10,9,2,5,3,7,101,18]
test.lengthOfLIS(nums)
4