题目
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
示例 2:
输入:nums = [0,1,0,3,2,3]
输出:4
示例 3:
输入:nums = [7,7,7,7,7,7,7]
输出:1
提示:
- 1 <= nums.length <= 2500
- -104 <= nums[i] <= 104
解法
动态规划 O(n2)
定义dp[i]表示截止第i个数的最长递增子序列的个数。则dp[i+1]等于0-i中比nums[i+1]小的值(设为j)的 max(dp[i + 1], dp[j])。最后所求结果为dp数组中最大值
/**
* 动态规划
* @param {number[]} nums
* @return {number}
*/
var lengthOfLIS = function (nums) {
// 默认每个值的最大递增子序列为1,因为包括自己
const dp = new Array(nums.length).fill(1);
let res = 1;
for (let i = 1; i < nums.length; i++) {
// 循环小于i的dp数组,找到比自己小的值
for (let j = i - 1; j >= 0; j--) {
if (nums[i] > nums[j]) {
// dp[i]表示截止i位的最大的最长递增子序列的个数
dp[i] = Math.max(dp[i], dp[j] + 1)
}
}
res = Math.max(res, dp[i])
}
return res;
};
贪心+二分 O(logn)
我们要求最长递增子序列,就是要子序列的末尾尽量的小。我们可以维护一个新数组arr,该数组下标的每一位表示长度为下标的最长递增子序列的末尾值。然后不停的找到长度一样但是值比当前值小的末尾值进行替换。
我们用二分在arr中找到大于当前值的最小值。
比如 [10,9,2,5,3,7,101,18], 初始化arr = [10];
进9,长度为1的递增子序列目前末尾最小值就是9,arr = [9];
进2,长度为1的递增子序列目前末尾最小值是2,arr = [2];
进5,5比2大,arr = [2, 5];
进3,去arr中找到大于3的最小值,替换,此时长度为2的递增子序列的末尾最小值是3, 3比5好,因为3可以有更多可能性,比如4,5可以放到3后面,而5不行, arr = [2, 3];
进7,arr = [2, 3, 7];
进101,arr = [2, 3, 7, 101];
进18,替换掉101,arr = [2, 3, 7, 18];
注意
arr保存的并不是当前的递增子序列,而是每个位置长度的递增子序列的最优末尾值。 从中间替换比末尾小的值不会影响之前的递增子序列,因为后面比末尾值大的数肯定还是加到末尾之后,而中间的递增子序列又进行了优化。
/**
* 贪心+二分
* @param {number[]} nums
* @return {number}
*/
var lengthOfLIS = function (nums) {
let arr = [nums[0]], len = 1;
for (let i = 1; i < nums.length; i++) {
if (nums[i] > arr[len - 1]) {
arr[len++] = nums[i];
continue;
}
let idx = binary_search(arr, nums[i]);
// 相等,不累计
if (idx === -1) continue;
// 这里只要维护递增子序列的最后一个数,这样后面来的数走到这步肯定比idx的值小,
// 代表该递增子序列最后一个值比之前的小,
// 这里替换掉原来的子序列就很划算,因为后面递增的可能性更多
arr[idx] = nums[i];
}
return len;
};
var binary_search = function (arr, target) {
let left = 0, right = arr.length - 1, mid;
// 找到在arr中找到大于当前值的最小值
while (left <= right) {
mid = left + ((right - left) >> 1);
if (arr[mid] > target) {
right = mid - 1;
} else if (arr[mid] < target) {
left = mid + 1
} else {
//相等,忽略
return -1;
}
}
return left;
}