题目
复杂度为O(n*n)的解:
let arr = [2, 7, 5, 3, 6, 4, 5, 6, 1];
//O(n*n)
function maxSubLen(arr) {
if (arr == null || arr.length == 0) {
return 0;
}
let n = arr.length;
//dp数组存储数组中以每个元素结尾的最长子序列值
let dp = [];
dp[0] = 1;
let max = dp[0];
for (let i = 1; i < n; i++) {
let pre = 0;
//遍历每个元素之前的元素,查找所有比该元素小的,且dp长度最大的那个
for (let j = 0; j < i; j++) {
if (arr[j] < arr[i]) {
pre = Math.max(pre, dp[j]);
}
}
//记录以该元素结尾的最长子序列值
dp[i] = pre + 1;
//查重记录的值中最大的
max = Math.max(max, dp[i]);
}
return max;
}
console.log(maxSubLen(arr));
复杂度为O(n*logn)的解:
再添加一个数组ends,用来帮助dp存储以当前元素结尾的子序列的长度,ends中存储的是当前元素,索引为当前元素的子序列的长度,每遍历一个元素时,会查找ends中第一个比该元素大的元素,然后将元素放置在该位置,如果没有找到,则说明序列长度应该增加,则数组长度加1,放在末尾
因为数组中没有比当前元素大的就扩容+1,表明序列长度+1;数组中能找到第一个比当前元素大的就替换,索引+1即为当前元素结尾的序列长度。因此ends中的元素是有序递增的,在查处时即可通过二分查找,使得时间复杂度为O(logN);
而ends数组的存放逻辑,即对应了人类查找的思维,比如[1,3,4,2];ends前三个数[1,3,4],第四个数2对应的递增序列应该为1,2,要放在ends中的位置即为第一个比2大的位置[1,2,4],索引+1即为长度
换种说法:每当ends数组中找到一个比当前元素大的元素,说明以当前元素结尾的序列和之前断掉了(不能构成递增序列),需要重新计算,而新的序列就是之前ends中比当前元素小的那些元素加上当前元素构成的序列
let arr = [2, 7, 5, 3, 6, 4, 4, 6, 1];
function maxSubLen2(arr) {
if (arr == null || arr.length == 0) {
return 0;
}
let n = arr.length;
let dp = [];
let ends = [];
dp[0] = 1;
ends[0] = arr[0];
//ends数组有效区
//0到validSize-1的范围二分
let max = dp[0];
let validSize = 1;
for (let i = 1; i < n; i++) {
let cur = arr[i];
//二分查处第一个比当前元素大的元素位置
let endsi = find(ends, validSize, cur);
//-1表示没有找到,说明ends数组有效区里都是小于num的,说明需要扩充有效区
if (endsi == -1) {
ends[validSize++] = cur;
//扩容后的长度就是新增元素的序列长度
dp[i] = validSize;
} else {
ends[endsi] = cur;
//第一个比当前元素大的元素位置+1,就是以当前元素结尾的序列长度
dp[i] = endsi + 1;
}
max = Math.max(max, dp[i]);
}
return max;
}
//ends数组有效区里查找>=num最左的位置,没有则返回-1,二分查找
function find(ends, size, num) {
let left = 0;
let right = size - 1;
let min = -1;
let mid = 0;
//left即代表第一个比当前元素大的元素位置
while (left <= right) {
mid = left + parseInt((right - left) / 2);
if (ends[mid] > num) {
right = mid - 1;
} else {
//等于当前元素的元素,直接替换位置即可
if (ends[mid] == num) {
left = right = mid;
break;
}
left = mid + 1;
}
}
// console.log(left);
// console.log(right);
//没有找到,说明数组元素都比当前元素小
if (left == size) {
return -1;
}
return left;
}

题目
let arr3 = [1, 3, 5, 7, 9, 11, 21];
function maxSubLen3(arr) {
if (arr == null || arr.length == 0) {
return 0;
}
let n = arr.length;
let dp = [];
let ends = [];
dp[0] = 1;
ends[0] = arr[0];
//ends数组有效区
//0到validSize-1的范围二分
let max = dp[0];
let validSize = 1;
for (let i = 1; i < n; i++) {
let cur = arr[i];
let endsi = find2(ends, validSize, cur);
//ends数组有效区里都是小于num的,说明需要扩充有效区
if (endsi == validSize - 1) {
ends[validSize] = ends[validSize - 1] + cur;
dp[i] = ++validSize;
} else {
//根据查找到的第一个比该元素小的元素和该元素的和(累加重量),如果小于已有的,则进行替换
if (ends[endsi] + cur < ends[endsi + 1]) {
ends[endsi + 1] = ends[endsi] + cur;
// endsi + 1 表示该元素的位置,再+1表示长度
dp[i] = endsi + 1 + 1;
} else {
dp[i] = endsi + 1 + 1;
}
}
max = Math.max(max, dp[i]);
}
return max;
}
//ends数组有效区里第一个比num小的位置
function find2(ends, size, num) {
let left = 0;
let right = size - 1;
let mid = 0;
let res = 0;
while (left <= right) {
mid = left + parseInt((right - left) / 2);
if (ends[mid] <= num) {
res = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}
return res;
}
console.log(maxSubLen3(arr3));