简介
最长递增子序列在Vue2和Vue3的源码中都有该问题的算法,通过找到最长的递增子序列来达到减少dom操作更新视图的目的 而Vue3在diff算法的时候,采用了更加高效的算法,进行最少的dom移动操作达到视图更新的目的
相关问题
给你一个整数数组 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
这是leetcode的问题leetcode.cn/problems/lo…
动态规划
该运算比较好理解,复杂度为O(n²)
思路:如果要求一个数组nums的最长递增子序列,那就反着推,最后一个数的最大序列是从前面所有比它小的数字中选一个最长的+1,依次往前类推,就能得到答案
实现:创建一个dp数组,长度跟nums数组相同,初始化里面的值都为1,表示每一个数字最少都是1个长度;
然后循环nums,从nums的前i数组中利用Math.max函数获取max,dp[i] = max + 1;
最后返回dp中最大值就是最长递增子序列的长度
function lengthOfLIS(nums: number[]): number {
const n = nums.length
if (n <= 1) return n
// 设置一个dp数组,其中存放的是跟nums长度相同的,初始化为1的数组
const dp = Array.from({length: n}, () => 1)
for(let i = 1; i< n;i++) {
let max = 0
for(let j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
max = Math.max(max,dp[j])
}
}
dp[i] = max + 1
}
return Math.max(...dp)
};
在做解题的时候,注意数组nums中可能有相同的值
贪心+二分查找获取长度
这个是目前Vue3所采用的策略,比较难理解,复杂度为O(nlogn)
思路:在一个新的dp数组中依次加入nums的数字,如果加入的数字大于dp数组最后一个数字,那么就直接加入,否则,就二分找到离这个数最近的大于这个数的数字的位置,然后当前数字替换这个大点的数字,最后返回这个dp数组的长度即可
实现:通过在dp数组中一直新增或者替换元素,最终dp元素的长度就是最长递增长度,但是dp数组中的元素不一定是正确数组
function lengthOfLIS(nums: number[]): number {
const n = nums.length
if (n <= 1) return n
const dp = [nums[0]]
for(let i = 1;i< n;i++) {
const cur = nums[i]
if (cur > dp[dp.length - 1]) {
dp.push(cur)
} else if (cur < dp[dp.length - 1]) {
// 二分替换最近的右边元素
let left = 0
let right = dp.length - 1
while(left < right) {
const mid = (left + right) >> 1
if (cur > dp[mid]) {
left = mid + 1
} else if (cur < dp[mid]){
right = mid
} else {
left = mid
right = mid
}
}
dp[left] = cur
}
}
return dp.length
};
贪心+二分查找获取正确的数组
这个问题解法比较复杂
个人理解:p数组用来记录索引,用一个p数组,来记录每次操作dp数组的时候,新增或者替换的元素的前一个元素在nums中的索引值,
比如是[undefined,1,3,5],然后在根据p数组,来修正dp数组,得到正确的最长递增子序列数组;
tail数组记录贪心+二分之后的数字,tail的长度是准确的,但是里面记录的数字不一定正确
所以有了p数组之后,tail的最后一个数字的索引是正确的,因为最后一位肯定是正确的,然后从最后一位往前推导,用最后一位数组的下标值index在p中获取前一位的下标值index2=p[index],然后tail[倒数第二位]=nums[index2],然后再通过index2找到index3=p[index2],然后tail[倒数第三位]=nums[index3],依次类推,碰到undefind代表已经替换完正序第一位了,就直接结束返回tail
function lengthOfLIS(nums: number[]): number[] {
const n = nums.length;
if (n <= 1) {
return n;
}
const p = [];
const tail = [0];
for (let i = 1; i < n; i++) {
const cur = nums[i];
if (cur > nums[tail[tail.length - 1]]) {
tail.push(i);
p[i] = tail[tail.length - 2];
} else if (cur < nums[tail[tail.length - 1]]) {
// 二分法在tail里找到 第一个大于cur的值
let left = 0;
let right = tail.length - 1;
while (left < right) {
const mid = (left + right) >> 1;
if (cur < nums[tail[mid]]) {
right = mid;
} else if (cur > nums[tail[mid]]) {
left = mid + 1;
} else {
left = mid;
right = mid;
}
}
tail[left] = i;
p[i] = tail[left - 1];
}
}
let start = tail.length;
let end = tail[start - 1];
while (start-- > 0) {
if (end != undefined) {
tail[start] = end;
end = p[end];
} else {
start = -1;
}
}
return tail;
}