https://www.nowcoder.com/practice/9cf027bf54714ad889d4f30ff0ae5481?tpId=117&&tqId=35013&rp=1&ru=/ta/job-code-high&qru=/ta/job-code-high/question-ranking
题目描述
给定数组arr,设长度为n,输出arr的最长递增子序列。(如果有多个答案,请输出其中字典序最小的)
示例1
输入
[2,1,5,3,6,4,8,9,7]
返回值
[1,3,4,8,9]
示例2
输入
[1,2,8,6,4]
返回值
[1,2,4]
说明
其最长递增子序列有3个,(1,2,8)、(1,2,6)、(1,2,4)其中第三个字典序最小,故答案为(1,2,4)
思路参考:
非原创,百度一波总结下来的:
一共需要两个辅助数组和一个辅助变量:
dp数组:用来存储位置i对应的最长子序列的长度
end数组:用来存储长度为i的子序列的最后一个元素的最小值
len:用来记录当前找到的最长子序列的长度
举个例子
[3,2,5,8,6,7]
end数组:
i=0: 3 长度为1的子序列为:3
end=[3]
i=1:2<3 长度为1的子序列为:2(用2替换3,因为这是是最容易使子序列扩展为长度为2的子序列的值)
end=[2]
i=2:5>2 所以长度为1的子序列可以扩展成一个长度为2的子序列
长度为1的子序列为:2
长度为2的子序列为:2 5
end=[2 5]
i=3:8>5 所以长度为2的子序列可以扩展成一个长度为3的子序列
长度为1的子序列为:2
长度为2的子序列为:2 5
长度为3的子序列为:2 5 8
end=[2 5 8]
i=4:6>5&&6<8 所以长度为2的子序列可以扩展成一个长度为3的子序列
长度为1的子序列为:2
长度为2的子序列为:2 5
长度为3的子序列为:2 5 6(用6替换8,因为只是最容易使子序列扩展为长度为4的子序列的值)
end=[2 5 6]
i=5:7>6 所以长度为3的子序列可以扩展成一个长度为4的子序列
长度为1的子序列为:2
长度为2的子序列为:2 5
长度为3的子序列为:2 5 6
长度为4的子序列为:2 5 6 7
end=[2 5 6 7]
笔记:我们用end[i]来存储长度为i+1的子序列的最后一个元素的最小值,因为这是最有希望使子序列扩展的值
当a[x]>end[i]时,那么长度为i+1的子序列必定可以扩展成一个i+2的子序列,如果(old=end[i+1])>a[x],那么就令
end[i+1]=a[x],他的含义是只要a[y]>a[x]即使a[y]<old,那么长度为i+2的子序列也必然可以扩展为一个长度为i+3的子序列。
所有最终结束时end数组为[2,5,6,7]即end[i]表示长度为(i+1)的子序列的最后一个元素值。
遍历结束后也会生成 dp数组:
arr:[3,2,5,8,6,7]
dp :[1 1 2 3 3 4]
len=4
dp[i]表示到arr[i]为止最长的递增子序列,得到字典序最小的元素只需要倒过来遍历dp,依次输出最先遇到的长度为len,len-1,len-2…1的arr元素即可。
res用来存储字典序最小的最长子序列。
即: len=4 dp[5]==4 ==>res[3]=arr[5]=7
len=3 dp[4]==3 ==>res[3]=arr[4]=6
dp[3]==3 pass因为需要的是字典序最小的
len=2 dp[2]==2 ==>res[3]=arr[2]=5
len=1 dp[1]==1 ==>res[3]=arr[1]=2
dp[0]==1 pass因为需要的是字典序最小的
结果:res=[2,5,6,7]
笔记:设dp[x]==dp[y]&&x<y那么a[x]必定大于a[y],因为如果a[x]<a[y],那么dp[x]对应的子序列必定可以扩展为长度dp[x]+1的子序列即dp[y]=dp[x]+1>dp[x],矛盾,所以a[x]>a[y]
import java.util.*;
public class Solution {
/**
* retrun the longest increasing subsequence
* @param arr int整型一维数组 the array
* @return int整型一维数组
*/
public int[] LIS (int[] arr) {
// write code here
// DP[] 代表前i格字符内最长递增序列的递增位数
if(arr.length <=1){
return arr;
}
//一共需要两个辅助数组和一个辅助变量:
//dp数组:用来存储位置i对应的最长子序列的长度
//end数组:用来存储长度为i的子序列的最后一个元素的最小值
//len:用来记录当前找到的最长子序列的长度
int[] DP = new int[arr.length];
int[] end = new int[arr.length]; // end数组大小不确定,直接取最大
DP[0] = 1;
end[0] = arr[0];
int len = 1;
for(int i = 1; i < arr.length; i++){
if(arr[i] > end[len - 1]){ // 如果某个元素比当前最长子序列的最大元素还大,直接加到序列末尾
end[len++] = arr[i];
DP[i] = len;
continue;
}
// 如果不是,则找到该元素所在的位置,必须用二分,否则会超时!
int index = findFirstIndex(end, len, arr[i]);
end[index] = arr[i];
DP[i] = (index+1);
}
int[] res = new int[len];
for(int k = arr.length - 1; k >=0; k--){
if(len == 0){
break;
}
if(DP[k]==len){
res[len-1] = arr[k];
len--;
}
}
return res;
}
private int findFirstIndex(int[] end, int len, int key){
int left = 0;
int right = len-1;
while(left <= right){
int mid = (left + right)>>1;
if(end[mid] < key){
left = mid + 1;
}else{
right = mid - 1;
}
}
//return end[left]<key ? (left+1):left;
return left;
}
}