题目
解一:暴力法:T(n) = O(2^n * n)
原理:[10, 9, 2, 5, 3, 7, 101, 18]
0 0 0 0 0 0 0 0
1 1 1 1 1 1 1 1
1) 每个位都有两种情况,存在与不存在,总共就是 O(2^n)
2) 再进行判断是否有序 O(n)
解二:动态规划:T(n) = O(n * n)
以 i 结尾的最长上升子序列作为状态
原理:比如 [2, 5, 3, 7],知道 [2, 5, 3] 并记录起来,就可以推出 [2, 5, 3, 7] 不需要每次都重新计算
自顶向下,即递归+备忘录
自底向上,即动态规划
1) 使用动态规划,可以把前面 O(2^n) 优化为 O(n)
2) 以 i 结尾的最长上升子序列,还要和前面每个数比较大小 O(n)
for (let i = 0; i < )
转移方程:
let max = -Infinity;
for (let j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
max = Math.max(max, DP[i]);
}
}
DP[i] = max;
解三:贪心算法 + 二分查找 T(n) = O(n * logn)
维护长度为 l 的有序子序列,且序列中每个值最小
原理:
-
不断维护每个值最小的上升子序列,一边遍历,一边维护,当遍历完的时候,这个上升子序列就是最长上升子序列(贪心算法)
-
一边遍历,一边维护,维护是指判断 nums[i] 能不能加进 维护的序列里, 如果可以,加在哪个位置
如果可以加,把 nums[i] 放进有序子序列。有序、数组、静态,这3个前提条件,查找某个元素位置,或者某个元素放在哪个位置,可以使用二分查找法
核心代码:
for (let i = 0; i < nums.length; i++) {
1)判断是否可以插入有序数组
2)使用二分查找,找到 nums[i],应该插入有序数组的位置
}
解三的完整代码
var lengthOfLIS = function (nums) {
if (nums.length <= 1) { return nums.length; }
const sortedNums = [nums[0]];
for (let i = 1; i < nums.length; i++) {
const lastNum = sortedNums[sortedNums.length - 1];
if (lastNum > nums[i]) {
// 在 sortedNums 进行二分查找法 插入 nums[i]
const target = nums[i];
let left = 0,
right = sortedNums.length - 1,
mid = -Infinity;
while (left <= right) {
mid = left + ((right - left) >> 1);
if (sortedNums[mid] > target) {
right = mid - 1;
} else if (sortedNums[mid] < target) {
left = mid + 1;
} else {
break;
}
}
if (mid === target) {
// Case: [4,10,4,3,8,9]
} else if (target > sortedNums[mid]) {
sortedNums[mid + 1] = target;
} else if (target < sortedNums[mid]) {
sortedNums[mid] = target;
}
} else if (lastNum === nums[i]) {
// Case: [2,2] Excepted: 1 Not: 2
continue;
} else {
sortedNums.push(nums[i]);
}
}
return sortedNums.length;
};