最长上升子序列的O(nlogn)解法

961 阅读2分钟

题目

Leetcode-最长上升子序列

解一:暴力法: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 的有序子序列,且序列中每个值最小

原理:

  1. 不断维护每个值最小的上升子序列,一边遍历,一边维护,当遍历完的时候,这个上升子序列就是最长上升子序列(贪心算法)

  2. 一边遍历,一边维护,维护是指判断 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;
};

参考资料

liweiwei1419- 动态规划 、优化(以贪心和二分作为子过程)