给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:
- 可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
- 算法的时间复杂度应该为 O ( n 2 ) O(n^2) O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?
要找无序数组中的最长上升子序列,相当于找所有的上升子序列中最长的那个。因此,如果我们能找到所有可能的上升子序列,那么长度最长的那个不就是想要的结果吗?因此,最直接的方法就是暴力法,依次判断从当前数出发,后续列表中的数据能否构成上升序列:
- 如果当元素比已有上升序列的最后一个元素大,那么将其加到序列尾部,继续往后判断
- 否则,保存当前的上升序列,然后将当前元素作为新的上升序列的第一个元素,继续往后判断
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
if not nums: return 0
if len(nums) == 1: return 1
r = [[-99999]]
for i in range(len(nums)):
for j in range(len(r)):
if nums[i] > r[j][-1]:
t = r[j].copy()
t.append(nums[i])
r.append(t)
else:
r.append([nums[i]])
maxL = 1
for i in r:
if len(i) >= maxL:
maxL = len(i)
return maxL - 1
但是它不能通过所有的测试用例,当数组的长度很长时,算法的执行会超出时间限制。
题目要求返回的是最长上升序列的长度,而不是具体的序列。因此,在计算的过程中只需要记录可能的序列的长度即可。
我们建立一个数组来记录到当前元素为止,可能的上升序列的长度。由于每一个元素单独可看做是上升序列,因此,数组元素的初始值都是1。假设访问到第i个元素nums[i]时,前面的子序列对应的dp数组的记录更新,那么判断nums[i]能否构成上升序列时,只需要判断数组中 0 ∼ i 0 \sim i 0∼i的元素和nums[i]的大小即可:
-
如果 0 ∼ i 0 \sim i 0∼i中的元素都大于
nums[i],那么它们和nums[i]就无法构成上升序列,那么dp[i]的值就不发生改变 -
如果
nums[i]大于其中的某一个元素,那么加上nums[i]依然可以构成上升序列,那么dp[i]的值就可能发生改变,具体的规则是 d p [ i ] = max ( d p [ i ] , d p [ j ] + 1 ) dp[i] = \max(dp[i], dp[j] + 1) dp[i]=max(dp[i],dp[j]+1)
经过分析,我们便得到了上述的动态转移方程,最后只需按照动态规划的方法求解即可。解题代码如下:
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
if not nums: return 0
dp = [1] * len(nums)
for i in range(len(nums)):
for j in range(i):
if nums[j] < nums[i]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)
Java解题代码如下:
class Solution {
public int lengthOfLIS(int[] nums) {
if(nums == null || nums.length == 0){
return 0;
}
Integer[] dp = new Integer[nums.length];
Arrays.fill(dp, 1);
for(int i = 0; i < nums.length; i++){
for(int j = 0; j < i; j++){
if(nums[j] < nums[i]){
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
}
return (int)Collections.max(Arrays.asList(dp));
}
}