题目描述
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
输入:nums = [10,9,2,5,3,7,101,18] 输出:4 解释:最长递增子序列是 [2,3,7,101],因此长度为 4
求解思路
动态规划思想。从前往后遍历,用bp Map保存当前最长子段的长度和当前最长子段的尾数(要记录当前的最小值)
eg:nums = [10,9,2,5,3,7,101,18]
动态过程:
nums | bp Map |
10 | <1,10> |
9 | <1,9> |
2 | <1,2> |
5 | <1,2><2,5> |
3 | <1,2><2.3> |
7 | <1,2><2,3><3,7> |
101 | <1,2><2,3><3,7><4,101> |
18 | <1,2><2,3><3,7><4,18> |
代码
执行用时:73 ms, 在所有 Java 提交中击败了60.54%的用户
内存消耗:38.8 MB, 在所有 Java 提交中击败了5.32%的用户
class Solution {
public int lengthOfLIS(int[] nums) {
Map<Integer,Integer> bp = new HashMap<Integer,Integer>(); //key:当前最长子段的长度 value:当前最长子段的尾数(在所有可能中记录最小的)
bp.put(1,nums[0]); //初始化
int len = nums.length;
for(int i=1;i<len;i++){
ListIterator<Map.Entry<Integer,Integer>> li = new ArrayList<Map.Entry<Integer,Integer>>(bp.entrySet()).listIterator(bp.size());
int flag = 0;
while(li.hasPrevious()) { //逆序遍历map,从最长的开始
Map.Entry<Integer,Integer> entry = li.previous();
if(nums[i]>entry.getValue()){
if(flag==0){ //如果能加在最长的后面,直接增加长度
bp.put(entry.getKey()+1,nums[i]);
break;
}else if(bp.get(entry.getKey()+1)>nums[i]){//不能,但比当前最长的数值小,更新
bp.put(entry.getKey()+1,nums[i]);
break;
}
}
flag++;
}
if(flag==bp.size()&&(bp.get(1)>nums[i])){ //都不能,且比长度为1的子段的数值小,更新
bp.put(1,nums[i]);
}
}
return bp.size();
}
}
看完评论区之后的代码(用数组代替Map,效率提升了不少)
执行用时:10 ms, 在所有 Java 提交中击败了78.06%的用户
内存消耗:38 MB, 在所有 Java 提交中击败了71.78%的用户
class Solution {
public int lengthOfLIS(int[] nums) {
int[] dp = new int[nums.length];
dp[0] = nums[0];
int maxl = 1;
for(int i = 1;i<nums.length;i++){
// int flag = 0;
for(int j=maxl-1;j>=0;j--){
if(nums[i]>dp[j]){
if(j==maxl-1){
dp[j+1] = nums[i];
maxl++;
}else if(dp[j+1]>nums[i]){
dp[j+1] = dp[j+1] = nums[i];
}
}
if((j==0)&&nums[i]<dp[j]){
dp[j] = nums[i];
}
}
}
return maxl;
}}
更新!!!!
遍历到每个数时找这个数应该插入到哪里的过程变化:将原来的从后往前查找------>变为二分查找,时间复杂度降为了O(NlogN)