【修訂版】Leetcode 300 最長遞增子序列

一杯綠茶發表於2022-03-13

給你一個整數陣列 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 。

示例 2

輸入:nums = [0,1,0,3,2,3]
輸出:4

示例 3

輸入:nums = [7,7,7,7,7,7,7]
輸出:1

解題思路

常規方法是使用動態規劃,時間複雜度 O(n^2)

class Solution {
    public int lengthOfLIS(int[] nums) {
        // 定義 dp 陣列
        // dp[i] 表示以 nums[i] 這個數結尾的最長遞增子序列長度
        int[] dp = new int[nums.length];
        // 初始值填充 1(子序列至少包含當前元素自己)
        Arrays.fill(dp, 1);
        for (int i = 0; i < nums.length; i++) {
            for (int j = 0; j < i; j++) {
                // 假設 dp[0...i-1] 都已知,需要求出 dp[i]
                // 只需要遍歷 nums[0...i-1],找到結尾比 nums[i] 小的子序列 dp[j]
                // 然後把 nums[i] 接到最後,就可以形成一個新的遞增子序列,長度為 dp[j] + 1
                // 顯然,可能形成很多種新的子序列,只需要選擇最長的,作為 dp[i] 的值即可
                if (nums[i] > nums[j]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
        }
        // 遍歷 dp 陣列,找出最大值
        int res = 0;
        for (int i = 0; i < dp.length; i++) {
            res = Math.max(res, dp[i]);
        }
        return res;
    }
}

這邊採用 Vue 中 DOM diff 的思路,即貪心法,需要注意的是,最後 stack 的長度是對的,但是內容可能不是正確的。由於採用了兩層迴圈遍歷,時間複雜度為 O(n^2)

var lengthOfLIS = function (nums) {
    let stack = [];
    for (let i = 0; i < nums.length; i++) {
        // 陣列為空直接入棧,不為空則獲取棧頂元素判斷大小
        if (stack.length == 0 || getTopEle(stack) < nums[i]) {
            stack.push(nums[i]);
        } else {
            let index = findNextEle(stack, nums[i]);
            stack[index] = nums[i];
        }
    }
    return stack.length;
};

function getTopEle(arr) {
    if (!arr.length) return 0;
    return arr[arr.length - 1];
}

function findNextEle(arr, n) {
    // 判斷大小用 >= ,即不替換棧頂元素
    return arr.findIndex(item => item >= n);
}

進一步優化,可以將 findIndex 方法替換為二分查詢,時間複雜度降低到 O(nlogn)

參考

精讀《DOM diff 最長上升子序列》

相關文章