LeetCode——最長上升子序列/最長遞增子序列的個數?


Q:給定一個無序的整數數組,找到其中最長上升子序列的長度。

示例:
輸入: [10,9,2,5,3,7,101,18]
輸出: 4
解釋: 最長的上升子序列是 [2,3,7,101],它的長度是 4。

說明:
可能會有多種最長上升子序列的組合,你只需要輸出對應的長度即可。
你算法的時間復雜度應該為 O(n2) 。
進階: 你能將算法的時間復雜度降低到 O(n log n) 嗎?

A:找最長遞增子序列長度:(這個比較簡單,就找了一個現成的)

public int lengthOfLIS(int[] nums) {
    int[] dp = new int[nums.length];
    // dp 數組全都初始化為 1
    Arrays.fill(dp, 1);
    for (int i = 0; i < nums.length; i++) {
        for (int j = 0; j < i; j++) {
            if (nums[i] > nums[j])
                dp[i] = Math.max(dp[i], dp[j] + 1);
        }
    }
    int res = 0;
    for (int i = 0; i < dp.length; i++) {
        res = Math.max(res, dp[i]);
    }
    return res;
}

但這樣的復雜度為O(N^2),可以對最長子序的數組進行維護
此處引用:一步一步推導出官方最優解法,詳細圖解
從一個例子開始。我們先考慮下面的數組的最長上升子序列:
[10, 9, 2, 5, 3, 7, 101, 4, 1]
用暴力法求解,可以得下圖:

假如我們要在里面新增一個元素 X,希望找出插入 X 之后的最長子序列。
結論一指出,我們需要在當前允許插入的 最長 子序列之后添加元素。
於是,我們可以依次檢查序列長度 = 1,2,3,4 的遞增子序列,然后找出最長的,尾數 < X 的序列。
我們發現,對每一個序列長度 l,只需要檢查圖中的每一列的最小值(綠色的元素)是否 < X 即可。如果綠色的元素 < X,表明長度為 l 的遞增子序列后可添加元素 X。
因此,我們有 結論二:我們只需要維護 長度為 l 的遞增子序列的 最小結尾數字。
我們應該如何實現這樣的算法呢?
最簡單的實現方式,當插入新元素 X 時,我們從 1 逐個枚舉現有遞增子序列的長度,直到找到最大可添加元素 X 的長度。與此同時,維護每個長度 l 的最小尾數:

比如前述序列 [10, 9, 2, 5, 3, 7, 101, 4, 1],已構造 dp 數組[1, 3, 4, 101],要添加 “6”。
長度 l = 1 時,長度為 1 的遞增子序列末尾的最小數字為 1,6 > 1,可以添加。
l = 2 時,6 > 3,可以添加。
l = 3 時,6 > 4,可以添加。
l = 4 時,6 < 101,不可添加。
因此,以 “6” 為結尾的遞增子序列最長為 3 + 1 = 4。
另外,此時,長度為 4 的遞增子序列的最小尾數變成了 “6”。因此修改 “101” -> “6”。數組變為 [1, 3, 4, 6]。

由於數組是有序的,當要添加數 “x” 時,可以用二分搜索找出數組中小於 x 的最大數字。

    public int lengthOfLIS(int[] nums) {
        int n = nums.length;
        if (n <= 1)
            return n;
        ArrayList<Integer> array = new ArrayList<>();
        array.add(nums[0]);
        for (int i = 1; i < n; i++) {
            int index = Binary(array, nums[i], 0, array.size());//二分法查找該替代的位置
            if (index == array.size())
                array.add(nums[i]);//如果比最大的還大,放到隊尾
            else {
                array.set(index, nums[i]);//取代當前位置
            }
        }
        return array.size();
    }

    private int Binary(ArrayList<Integer> array, int num, int start, int end) {
        int left = start, right = end;
        while (left < right) {
            int mid = (left + right) / 2;
            if (array.get(mid) == num) {
                right = mid;
            } else if (array.get(mid) > num) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }

Q:給定一個未排序的整數數組,找到最長遞增子序列的個數。

示例 1:
輸入: [1,3,5,4,7]
輸出: 2
解釋: 有兩個最長遞增子序列,分別是 [1, 3, 4, 7] 和[1, 3, 5, 7]。
示例 2:
輸入: [2,2,2,2,2]
輸出: 5
解釋: 最長遞增子序列的長度是1,並且存在5個子序列的長度為1,因此輸出5。
注意: 給定的數組長度不超過 2000 並且結果一定是32位有符號整數。

A:
1.基於上一題的第一種解法,同樣使用動態規划。找個數時,放一個count數組。

public int findNumberOfLIS(int[] nums) {
        if (nums.length <= 1)
            return nums.length;
        int n = nums.length;
        int[] length = new int[n];
        int[] count = new int[n];
        Arrays.fill(length, 1);
        Arrays.fill(count, 1);

        int maxLen = 0;
        for (int i = 1; i < n; i++) {
            for (int j = 0; j < i; j++) {
                if (nums[i] > nums[j]) {
                    if (length[i] < length[j] + 1) {//更新最大長度子序列
                        length[i] = length[j] + 1;
                        count[i] = count[j];
                    } else if (length[i] == length[j] + 1) {//同樣以i結尾的最大長度的子序列
                        count[i] += count[j];
                    }
                }
            }
            maxLen = Math.max(maxLen, length[i]);
        }
        int res = 0;
        for (int i = 0; i < n; i++) {
            if (maxLen == length[i])
                res += count[i];
        }
        return res;
    }

2.基於上一題的第二種寫法,用樹狀數組做
3.用CDQ分治
(這兩個找時間再看吧)O(nlogn)的兩種解法:樹狀數組和CDQ分治(新手慎入)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM