最長遞增子序列 與 俄羅斯套娃

最長遞增子序列 (Longest Increasing Subsequence)

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

T=O( N2 )


//nums:輸入數組
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;
}

T=O( N*log(N) )

//蜘蛛紙牌思想
public int lengthOfLIS(int[] nums) {
    int[] top = new int[nums.length];
    // 牌堆數初始化爲 0
    int piles = 0;
    for (int i = 0; i < nums.length; i++) {
        // 要處理的撲克牌
        int poker = nums[i];

        /***** 搜索左側邊界的二分查找 *****/
        int left = 0, right = piles;
        while (left < right) {
            int mid = (left + right) / 2;
            if (top[mid] > poker) {
                right = mid;
            } else if (top[mid] < poker) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        /*********************************/
        // 沒找到合適的牌堆,新建一堆
        if (left == piles) piles++;
        // 把這張牌放到牌堆頂
        top[left] = poker;
    }
    // 牌堆數就是 LIS 長度
    return piles;
}

最長遞增子序列的動態規劃(T=O( N2 )和二分法查找(T=O( N*log(N) )))

俄羅斯套娃

Q:

給定一些標記了寬度和高度的信封,寬度和高度以整數對形式 (w, h) 出現。當另一個信封的 寬度和高度都比這個信封大 的時候,這個信封就可以放進另一個信封裏,請計算 最多能有多少個信封 能組成一組“俄羅斯套娃”信封(即可以把一個信封放到另一個信封裏面)。

思路:

先對 w 排升序,如果 w 相同 h 排降序(逆序排序保證在 w 相同的數對中最多隻選取一個符合條件的 h),然後在所有 h 中尋找最長遞增子序列。

題解:

時間複雜度爲 O( NlogN ),因爲排序和計算 LIS 各需要 O( NlogN )的時間。

空間複雜度爲 O( N ),因爲計算 LIS 的函數中需要一個 top 數組

// envelopes = [[w, h], [w, h]...]:爲輸入的寬高數組
public int maxEnvelopes(int[][] envelopes) {
    int n = envelopes.length;
    // 按寬度升序排列,如果寬度一樣,則按高度降序排列
    Arrays.sort(envelopes, new Comparator<int[]>() 
    {
        public int compare(int[] a, int[] b) {
            return a[0] == b[0] ? 
                b[1] - a[1] : a[0] - b[0];
        }
    });
    // 對高度數組尋找 LIS
    int[] height = new int[n];
    for (int i = 0; i < n; i++)
        height[i] = envelopes[i][1];

    return lengthOfLIS(height);
}

/* 
二分法查找 lis,T=O( N*logN )
返回 nums 中 LIS 的長度 */
public int lengthOfLIS(int[] nums) {
    int piles = 0, n = nums.length;
    int[] top = new int[n];
    for (int i = 0; i < n; i++) {
        // 要處理的撲克牌
        int poker = nums[i];
        int left = 0, right = piles;
        // 二分查找插入位置
        while (left < right) {
            int mid = (left + right) / 2;
            if (top[mid] >= poker)
                right = mid;
            else
                left = mid + 1;
        }
        if (left == piles) piles++;
        // 把這張牌放到牌堆頂
        top[left] = poker;
    }
    // 牌堆數就是 LIS 長度
    return piles;
}

俄羅斯套娃圖解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章