记录 1 道算法题
最长递增子序列
要求:提供一个数组,返回里面连续递增的子序列长度。
比如:[10,9,2,5,3,7,101,18]
,输出:4
最简单的做法就是双循环,然后计算当前的数可以加入前面多少个子序列中。
function lengthOfLIS(nums) {
// 首先默认每个数都是一个子序列
const arr = Array.from(nums).fill(1)
// 从第二位开始遍历当前数字
for(let i = 1; i < nums.length; i++) {
const n = nums[i]
// 判断当前数字是否跟前面的数字属于递增关系
for(let j = 0; j < i; j++) {
if (n > nums[j]) {
// 如果是递增就取前面那一个数的
arr[i] = Math.max(arr[j] + 1, arr[i])
}
}
}
// 最大的数就是最长的子序列的长度
return Math.max(...arr)
}
如果想要更低的时间复杂度,可以使用贪心加二分的解法。我们假设当遍历遇到更大的数字的时候则作为递增子序列的一部分,当遇到更小的数字的时候,如果将子序列中的数字替换成小的数字,那么后面能考虑的子序列的数字的范围会更大。
当我们遇到数组 [3, 7, 8, 4]
遍历到 4 的时候已知的子序列是 [3, 7, 8]
,
通过遍历子序列进行比较,将子序列的数字替换成这个更小的 4,
这时候子序列是 [3, 4, 8]
。
那么我们会看到即使这样替换了,最大的子序列长度依然是 3,不会对结果产生任何影响。
而当我们遇到数组 [3, 7, 8, 4, 5, 6]
的时候,
我们会发现子序列会变成 [3, 4, 5, 6]
,
可以正确的更新最大的子序列长度。
遍历子序列的时候我们找的是比当前遍历到的数大的数里面最小的那个数。我们可以通过二分进行查找。
function lengthOfLIS(nums) {
// 准备一个子序列的数组,默认第一个数
const arr = [nums[0]]
// 从第二个数开始遍历
for(let i = 1; i < nums.length; i++) {
// 最后一个数
const a = arr[arr.length - 1]
// 当前的数
const n = nums[i]
if (a < n) {
// 如果当前的数大,那就加入子序列
arr.push(n)
} else {
// 如果小,就替换掉子序列里比当前的数大的数中的最小的那个数
// 二分
let j = 0
let l = 0
let r = arr.length - 1
while(j <= r) {
const mid = (l + r) >> 1
if (arr[mid] < n) {
j = mid
l = mid + 1
} else {
r = mid - 1
}
}
arr[j + 1] = n
}
}
// 数组的长度就是最长的子序列长度
return arr.length
}