题目
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
来源:力扣(LeetCode)
链接:leetcode-cn.com/problems/lo…
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
⏰:在此提醒:
首先看题目,自己先试着分析一下,看看有什么思路,再去动手实现一下,最后再看接下来的分析与题解!
这样有助于自己提升自身的思考分析能力,同时能更好的找到自己的问题,快速吸收所需!
快去快回...
分析及题解
最长严格递增子序列的长度关键字:最长长度(只求最长长度,不用求具体最长子序列)、严格(必须大于,不能等于)、递增子序列
第一种方法:二分查找
- 维护一个子序列存放当前的递增序列
- 将当前数与子序列最大值比较,如果比最大值大则加入队尾,如果不大于则找一个合适的位置替换当前位置的元素
缺点:其实只能找到最长长度,算是类比出最长长度,占位得到最长长度,但是不能获取到真正的子序列。
/**
* @param {number[]} nums
* @return {number}
*/
var lengthOfLIS = function(nums) {
let n = nums.length;
if(n <= 1){
return n;
}
let tail = [nums[0]]; // 递增子序列
for(let i = 0;i < n;i++){
if(nums[i] > tail[tail.length-1]){ // 大于则加到队尾
tail.push(nums[i]);
}else{ // 不大于情况,找合适位置
let left = 0;
let right = tail.length-1;
while(left < right){
let mid = (left + right) >> 1;
if(tail[mid] < nums[i]){ // 二分查找,找合适位置
left = mid + 1;
}else{
right = mid;
}
}
tail[left] = nums[i];
}
}
console.log(tail)
return tail.length;
};
友情赠送:二分查找
二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。
二分查找之所以快是因为它只需检查很少几个条目(相对于数组的大小)就能够找到目标元素,或者是确认目标元素不存在。
友情赠送:右移运算符(>>)
定义:将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。
例如:a=a>>2 将a的二进制位右移2位,左补0 或者 左补1得看被移数是正还是负。
操作数每右移一位,相当于该数除以2。
第二个方法:动态规划
- 这种问题一般就可以联想到动态规划,那就dp开始。
- dp[i]含义:从0到下标为i的序列的最长子序列长度。
- 最终结果(子序列的最大长度)应该是 dp 数组中的最大值。
缺点:第二个for循环比较浪费资源,把当前的和之前的全部又比较了一次,算是比较暴力的解法。
/**
* @param {number[]} nums
* @return {number}
*/
var lengthOfLIS = function (nums) {
const n = nums.length
if(n<=1) return n
const dp = new Array(n).fill(1); // 初始化 dp
for (let i = 0; i < n; i++) {
// i与i前面的元素比较
for (let j = 0; j < i; j++) {
// 找比i小的元素,找到一个,就让当前序列的最长子序列长度加1
if (nums[i] > nums[j]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
}
// 找出最大的子序列
return Math.max(...dp);
};
第三种方法:动态规划+二分查找
- 在第二种方法上去优化那个暴力比较那一步。
- dp[i]含义:从0到下标为i的序列的最长子序列尾部的最小值。
- dp[0]没用,设置为null;dp[1]初始值nums[0]。
- 如果nums[i] 大于 dp中所有元素,则加入dp尾部;如果不大于则覆盖掉比它大的那个元素。
/**
* @param {number[]} nums
* @return {number}
*/
var lengthOfLIS = function (nums) {
const n = nums.length
if(n<=1) return n
const dp = [null, nums[0]]; // 初始化 dp
let max = 1
for (let i = 1; i < n; i++) { // 从1开始
// i与dp 最大的元素比较
if(nums[i]>dp[max]) { // 大于,加入尾部
dp[++max] = nums[i]
}else{ // 小于,找位置
let left = 1, right = max, mid, pos = 0
while(left<=right){ // 二分查找
mid = (left+right) >> 1
if(nums[i]>dp[mid]) { // 在右边
left = mid + 1
pos = mid
}else{// 在左边
right = mid -1
}
}
dp[pos+1] = nums[i]
}
}
console.log(dp)
return max
};
似乎和第一种方法比较接近。哈哈。。。
还有很多比较好的方法,如果大家有更优解,可以在评论区留言附上代码,大家一同进步!加油⛽️!