300. 最长递增子序列
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
解: 分析以nums[i]元素作为子序列结尾时,它能形成的最长子数组是多少。
可能性分析: 第i个元素之前没有值比它小的, maxI(i的最长结果)为1;有值比它小的,maxI为比它小且最长的 + 1
动态规划:从1开始遍历,每个位置都往前遍历一遍,找到值比它小并且更长的元素的结果。计算完毕之后放入dp数组,并且判断是否全局最大。问题就在于每个元素需要遍历两次。
所以改变dp数组的逻辑,dp的元素为一个数组,第0项是nums[i],第1项是它能形成的最长子数组为多长。然后以第0项的值的大小为基准形成单调递增数组。遍历nums时,如果nums[i]比dp最后一项还要大,那就直接加入,并且它的长度+1。如果不比dp最后一项的值大,那就二分查找找到第一个比它大的元素,用nums[i]替换掉dp中那个元素(因为nums[i]比那个元素更小,所以就递增的可能性来看,nums[i]一定>=那个元素,所以不再需要那个元素了)。当遍历结束,最大长度就是dp的最后一项的结果
const lengthOfLIS = function(nums) {
const dp = [[nums[0], 1]]
for (let i = 1; i < nums.length; i++) {
findFirstBigItem(nums[i])
}
return dp[dp.length - 1][1]
function findFirstBigItem(num) {
let left = 0
let right = dp.length - 1
let res = -1
while (left <= right) {
const midIdx = ~~((left + right) / 2)
if (dp[midIdx][0] >= num) {
right = midIdx - 1
res = midIdx
} else {
left = midIdx + 1
}
}
if (res === -1) {
const len = dp[dp.length - 1][1] + 1
dp[dp.length] = [num, len]
} else {
dp[res][0] = num
}
}
};