给定数组 arr ,设长度为 n ,输出 arr 的最长上升子序列。(如果有多个答案,请输出其中 按数值(注:区别于按单个字符的ASCII码值)进行比较的 字典序最小的那个)
数据范围:
要求:空间复杂度
,时间复杂度 )
[2,1,5,3,6,4,8,9,7]
[1,3,4,8,9]
[1,2,8,6,4]
[1,2,4]
其最长递增子序列有3个,(1,2,8)、(1,2,6)、(1,2,4)其中第三个 按数值进行比较的字典序 最小,故答案为(1,2,4)
我们知道通过贪心+二分可以求出最长递增子序列的长度,但由于每次更新len长度序列的结尾元素,导致了dp数组是真,结果不是最长的递增子序列,只能保证最长递增子序列的结尾元素。既然知道了失真的原因,那么就可以另外建立一个数组,在每次失真操作前保存序列。
具体:
function LIS( arr ) {
const dp = [];
let res = [];
for (let i=0; i<arr.length; ++i) {
let r = dp.length-1, l = 0;
let pos = dp.length;
while(l <= r) {
const mid = Math.floor((r - l) / 2) + l;
if (dp[mid] >= arr[i]) {
r = mid-1;
pos=mid;
} else if (dp[mid] < arr[i]){
l = mid+1;
}
}
// 获得递增序列
if(pos===dp.length) {
res.push(arr[i]);
} else if (pos === dp.length-1) {
res[res.length-1] = arr[i];
}
dp[pos] = arr[i];
}
return res;
}
超时版:
function LIS( arr ) {
if (arr === null || arr.length === 0) return 0;
const dp = new Array(arr.length).fill(1);
let max = 1;
for (let i = 1; i < arr.length; i++) {
for (let j = 0; j < i; j++) {
if (arr[i] > arr[j]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
max = Math.max(max, dp[i]);
}
}
}
const result = new Array(max);
for (let i = dp.length - 1; i >= 0; i--) {
if (dp[i] === max) result[--max] = arr[i];
}
return result;
} 通过版:
function LIS( arr ) {
const list = new Array(arr.length + 1);
const maxLen = new Array(arr.length);
let n = 1;
list[1] = arr[0];
maxLen[0] = 1;
for (let i = 0; i < arr.length; i++) {
if (list[n] < arr[i]) {
list[++n] = arr[i];
maxLen[i] = n;
} else {
const index = binarySearch(list, n, arr[i]);
list[index] = arr[i];
maxLen[i] = index;
}
}
const result = new Array(n);
for (let i = maxLen.length - 1; i >= 0; i--) {
if (maxLen[i] === n) result[--n] = arr[i];
}
return result;
}
function binarySearch(arr, right, key) {
let left = 0;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (arr[mid] >= key) right = mid - 1;
else left = mid + 1;
}
return left;
}