Leetcode 300.最长上升子序列
1 题目描述(Leetcode题目链接)
给定一个无序的整数数组,找到其中最长上升子序列的长度。
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:
- 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
- 你算法的时间复杂度应该为 O ( n 2 ) O(n^2) O(n2)
进阶: 你能将算法的时间复杂度降低到 O ( n l o g n ) O(n log n) O(nlogn) 吗?
2 题解
本题可以使用动态规划的方法来做,首先定义一个
D
P
DP
DP数组,长度与原数组长度相同,
D
P
[
i
]
DP[i]
DP[i]表示从数组开始元素到
n
u
m
s
[
i
]
nums[i]
nums[i],包含
n
u
m
s
[
i
]
nums[i]
nums[i]能组成的最长上升子序列的长度,所以
D
P
[
i
]
DP[i]
DP[i]的状态转移方程应该为:
i
f
n
u
m
s
[
i
]
>
n
u
m
s
[
j
]
:
D
P
[
i
]
=
m
a
x
(
D
P
[
j
]
+
1
,
D
P
[
i
]
)
j
=
0
⋯
i
−
1
if \ \ nums[i]>nums[j]:\ \ DP[i] = max(DP[j]+1, DP[i])\ \ \ j = 0\cdots i-1
if nums[i]>nums[j]: DP[i]=max(DP[j]+1,DP[i]) j=0⋯i−1
初始化 D P DP DP数组为全1,这样就是时间复杂度为 O ( n 2 ) O(n^2) O(n2)的动态规划。
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
if not nums:
return 0
length = len(nums)
DP = [1]*length;
for i in range(1,length):
for j in range(0, i):
if nums[i] > nums[j]:
DP[i] = max(DP[j]+1, DP[i])
return max(DP)
对于进阶要求的时间复杂度,有一个 l o g n logn logn,遍历数组就是 n n n,那么可能会增加一个二分法,这时候要重新定义 D P [ i ] DP[i] DP[i]为 i + 1 i+1 i+1长度的上升子序列的最小尾部值,那么 D P DP DP数组就是一个升序的数组,使用一个下标记录 D P DP DP数组的最后一个值,初始化 D P DP DP数组全0,遍历原数组时需要判断:
- 如果当前元素大于 D P DP DP最后一个值,那么需要将这个值加在 D P DP DP数组后;
- 如果当前元素小于 D P DP DP最后一个值,那么二分查找 D P DP DP,找到第一个比当前元素大的值,并替换之。
对于题目中的示例,此时的 D P DP DP数组变化情况如下:
原数组:nums = [10,9,2,5,3,7,101,18]
DP数组的变化:
DP = [10,0,0,0,0,0,0,0]
DP = [9,0,0,0,0,0,0,0]
DP = [2,0,0,0,0,0,0,0]
DP = [2,5,0,0,0,0,0,0]
DP = [2,3,0,0,0,0,0,0]
DP = [2,3,7,0,0,0,0,0]
DP = [2,3,7,101,0,0,0,0]
DP = [2,3,7,18,0,0,0,0]
最后这个记录的下标加1就是最长上升子序列的长度。
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
if not nums:
return 0
length = len(nums)
DP = [0]*length;
DP[0] = nums[0]
retv = 0
for i in range(1, length):
if nums[i] > DP[retv]:
retv += 1
DP[retv] = nums[i]
else:
left, right = 0, retv
while left < right:
mid = (left + right) // 2
if DP[mid] >= nums[i]:
right = mid
else:
left = mid + 1
DP[left] = nums[i]
return retv + 1