1. 程式人生 > >LeetCode-動態規劃總結(二)

LeetCode-動態規劃總結(二)

最長遞增子序列

已知一個序列 {S1, S2,…,Sn},取出若干陣列成新的序列 {Si1, Si2,…, Sim},其中 i1、i2 … im 保持遞增,即新序列中各個數仍然保持原數列中的先後順序,稱新序列為原序列的一個 子序列

如果在子序列中,當下標 ix > iy 時,Six > Siy,稱子序列為原序列的一個 遞增子序列

定義一個數組 dp 儲存最長遞增子序列的長度,dp[n] 表示以 Sn 結尾的序列的最長遞增子序列長度。對於一個遞增子序列 {Si1, Si2,…,Sim},如果 im < n 並且 Sim < Sn,此時 {Si1

, Si2,…, Sim, Sn} 為一個遞增子序列,遞增子序列的長度增加 1。滿足上述條件的遞增子序列中,長度最長的那個遞增子序列就是要找的,在長度最長的遞增子序列上加上 Sn 就構成了以 Sn 為結尾的最長遞增子序列。因此 dp[n] = max{ dp[i]+1 | Si < Sn && i < n} 。

因為在求 dp[n] 時可能無法找到一個滿足條件的遞增子序列,此時 {Sn} 就構成了遞增子序列,需要對前面的求解方程做修改,令 dp[n] 最小為 1,即:


對於一個長度為 N 的序列,最長遞增子序列並不一定會以 SN 為結尾,因此 dp[N] 不是序列的最長遞增子序列的長度,需要遍歷 dp 陣列找出最大值才是所要的結果,max{ dp[i] | 1 <= i <= N} 即為所求。

最長遞增子序列

300. Longest Increasing Subsequence (Medium)

class Solution {
    public int lengthOfLIS(int[] nums) {
        if (null == nums || 0 == nums.length) {
            return 0;
        }
        int n = nums.length;
        int[] dp = new int[n];
        Arrays.fill(dp, 1);
        int max = Integer.
MIN_VALUE; for (int i = 0; i < n; i++) { for (int j = 0; j <= i - 1; j++) { if (nums[i] > nums[j]) { dp[i] = Math.max(dp[i], dp[j] + 1); } } max = Math.max(max, dp[i]); } return max == Integer.MIN_VALUE ? 1 : max; } }

以上解法的時間複雜度為 O(N2),可以使用二分查詢將時間複雜度降低為 O(NlogN)。

定義一個 tails 陣列,其中 tails[i] 儲存長度為 i + 1 的最長遞增子序列的最後一個元素。對於一個元素 x,

  • 如果它大於 tails 陣列所有的值,那麼把它新增到 tails 後面,表示最長遞增子序列長度加 1;
  • 如果 tails[i-1] < x <= tails[i],那麼更新 tails[i] = x。

例如對於陣列 [4,3,6,5],有:

tails      len      num
[]         0        4
[4]        1        3
[3]        1        6
[3,6]      2        5
[3,5]      2        null

可以看出 tails 陣列保持有序,因此在查詢 Si 位於 tails 陣列的位置時就可以使用二分查詢。

class Solution {
    public int lengthOfLIS(int[] nums) {
        if (null == nums || 0 == nums.length) {
            return 0;
        }
        int[] tail = new int[nums.length];
        int len = 0;
        for (int num : nums) {
            int index = binarySearch(tail, len, num);
            tail[index] = num;
            if (index == len) {
                len++;
            }
        }
        return len;
    }
    public int binarySearch(int[] tail, int len, int target) {
        int left = 0, right = len;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (tail[mid] == target) {
                return mid;
            } else if (tail[mid] > target) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }
}

一組整數對能夠構成的最長鏈

646. Maximum Length of Pair Chain (Medium)

Input: [[1,2], [2,3], [3,4]]
Output: 2
Explanation: The longest chain is [1,2] -> [3,4]

題目描述:對於 (a, b) 和 (c, d) ,如果 b < c,則它們可以構成一條鏈。

class Solution {
    public int findLongestChain(int[][] pairs) {
        if (null == pairs || 0 == pairs.length) {
            return 0;
        }
        int m = pairs.length;
        int n = pairs[0].length;
        int[] dp = new int[m];
        Arrays.fill(dp, 1);
        int max = Integer.MIN_VALUE;
        Arrays.sort(pairs, new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o1[0] - o2[0];
            }
        });
        for (int i = 0; i < m; i++) {
            int c = pairs[i][0];
            for (int j = 0; j <= i - 1; j++) {
                int b = pairs[j][1];
                if (c > b) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
            max = Math.max(max, dp[i]);
        }
        return max == Integer.MIN_VALUE ? 1 : max;
    }
}

最長擺動子序列

376. Wiggle Subsequence (Medium)

Input: [1,7,4,9,2,5]
Output: 6
The entire sequence is a wiggle sequence.

Input: [1,17,5,10,13,15,10,5,16,8]
Output: 7
There are several subsequences that achieve this length. One is [1,17,10,13,10,16,8].

Input: [1,2,3,4,5,6,7,8,9]
Output: 2

要求:使用 O(N) 時間複雜度求解。

class Solution {
    public int wiggleMaxLength(int[] nums) {
        if (null == nums || 0 == nums.length) {
            return 0;
        }
        int n = nums.length;
        int up = 1, down = 1;
        for (int i = 1; i < n; i++) {
            if (nums[i] < nums[i - 1]) {
                down = up + 1;
            } else if (nums[i] > nums[i - 1]){
                up = down + 1;
            }
        }
        return Math.max(up, down);
    }
}