题目
链接:https://leetcode-cn.com/problems/longest-increasing-subsequence
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:
可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?
动态规化
设dp[i]
代表以nums[i]
结尾的数组最长子序列的长度
例如数组[10,9,2,5,3,7,101,18]
,假设已经知道了以下标Index
结尾的子数组的最长子序列长度为x
. 比如说index
为6,也就是子数组sub=[10,9,2,5,3,7,101]
,这时候x = 4 (2,3,7,101)
如果这时后加上一个数字18,这时求最长子序列的长度也就是要在子数组sub=[10,9,2,5,3,7,101]
使用一个指针j
从左向右遍历,如果sub[j] < 18
那么就可以和18构成一个子序列,也就是dp[j] + 1
,加上18后dp[i] (这里i为7)
要取几种情况中的最大值,所以状态转移方程为:
d p [ i ] = m a x ( d p [ j ] + 1 ) 0 < = j < i , n u m s [ j ] < n u m s [ i ] dp[i] = max(dp[j] + 1) 0<=j <i ,nums[j] < nums[i] dp[i]=max(dp[j]+1)0<=j<i,nums[j]<nums[i]
class Solution {
public int lengthOfLIS(int[] nums) {
int n = nums.length;
int[] dp = new int[n];
int maxLen = 0;
for (int i = 0; i < n; i++) {
int len = 1;
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
len = Math.max(len, dp[j] + 1); // 截止到j最长的长度+1
}
}
dp[i] = len;
maxLen = Math.max(maxLen, len);
}
return maxLen;
}
}
优化 使时间复杂度将为O(nlogn)
数组nums
的最长上升子序列的长度,不会超过其本身的长度n
,因此创建一个长度为n
的dp
数组,这个数组用来存放元素。
从左向右遍历数组nums
将元素插如dp
数组中,由于要求最长上升子序列的长度因此要使dp
数组中元素最多并保持上升状态。
如果当前要加入的元素的插入位置在dp
数组已加入元素的尾部,说明该元素比之前的元素都要大,比如dp=[2]
插入元素为5
,5>2
因此插入在尾部dp=[2,5]
如果当前元素没有比之前的元素都要大说明插入该元素不能使dp
中序列的长度变长,比如说插入3
, 此时 3<5
。因此需要查询该元素在dp
中的位置并替换该位置的元素,这样dp
中的序列就会减小就能右更多的元素插入在尾部,也就是说dp
从[2,5]
变为[2,3]
。
要知道元素的插入位置可以使用二分查找。
class Solution {
public int lengthOfLIS(int[] nums) {
int n = nums.length;
int len = 0;
int[] dp = new int[n];
for (Integer num : nums ) {
int index = Arrays.binarySearch(dp, 0,len, num);
if (index < 0) {
index = -index - 1;
}
dp[index] = num;
if (index == len) {
dp[index] = num;
len++;
}
}
return len;
}
}