俄羅斯套娃?一維二維“大魚喫小魚”算法

| 題引

俄羅斯套娃是俄羅斯特產的木製玩具,一般由多個一樣圖案的空心木娃娃一個套一個組成,最多可達十多個,通常爲圓柱形,底部平坦可以直立。顏色有紅色,藍色,綠色,紫色等。最普通的圖案是一個穿着俄羅斯民族服裝的姑娘,叫做“瑪特羅什卡”,這也成爲這種娃娃的通稱(如下圖所示)。
在這裏插入圖片描述


| 一維套娃 - 最長上升子序列

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

輸入: [2,6,1,9]
輸出: 3
解釋: 最長的上升子序列是[2,6,9],它的長度是 3

| 二維套娃 - 套娃信封

正題
給定一些標記了寬度和高度的信封,寬度和高度都爲正整數

當另一個信封的寬度和高度都比這個信封大的時候,這個信封就可以放進另一個信封裏,如同俄羅斯套娃一樣,不允許旋轉信封

計算最多能有多少個信封能組成一組“俄羅斯套娃”信封。
如:

輸入: [[5,4],[6,4],[6,7],[2,3]]
輸出: 3
解釋: 最多信封的個數爲 3, 組合爲: [2,3] => [5,4] => [6,7]

| 要點

一維套娃

  1. 順序不能改變
  2. 可能會有多個上升子序列,需要找到最長的那一個

二維套娃

  1. 順序可以改變
  2. 不能旋轉
  3. 寬度和長度必須同時是上升子序列

| 分析 - 最長上升子序列

一維套娃
2,6,1,9可以組成的上升子序列爲 [2, 6, 9] 和 [1, 9]
(其中一個數字暫不考慮,2,6和6,9可以算上升子序列,但屬於2,6,9,我們就可以不考慮這種組合了)
最長情況爲[2, 6, 9] ,所以長度是3
在這裏插入圖片描述

我們使用逆向思維推理一下(關於逆向思維可以看一下前一篇“趣味”or“燒腦”算法 之 王子救公主

如果我要計算出9這個位置最長上升子序列長度,那麼我就需要知道比9小的並且在9前面的所有的位置的最長上升子序列長度,如下圖:
在這裏插入圖片描述
再往前推,6的數字最大,我就推出6這個位置最長上升子序列長度了
以此類推…

所以程序設計時,我們從頭開始,將每一位的最長上升子序列計算出(以[10,9,2,5,3,7,98,18]爲例):
在這裏插入圖片描述


| 編碼 – 最長上升子序列

import java.util.Arrays;

/**
 * @author yanghao
 * @version LeetCode300Test.java, v 0.1 2019-11-19 16:44
 */
public class LeetCode300Test {

    public static void main(String[] args) {
        LeetCode300Test test = new LeetCode300Test();

        int[] nums = new int[]{10,9,2,5,3,7,98,18};

        System.out.println("result == " + test.leetCode300(nums));

    }

    public int leetCode300(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }

        int[] dp = new int[nums.length];
        //將數組全部填充爲1
        Arrays.fill(dp, 1);

        int max = 0;

        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[j] + 1, dp[i]);
                }
            }

            max = Math.max(max, dp[i]);
        }

        return max;
    }

}

| 分析 - 套娃信封

二維套娃
二維的解決思路可以參考一下一維的思想,重要點就是要保證寬和高同時是上升子序列,但是初始數據可能是一個無序的,所以我們可以先把數組按照寬度排序,那麼此時,高度就可以看成是一個一維套娃了~

注意點:寬度可能是有重複存在的,重複的數字是無法進行套娃的。

先把數組按寬度排序,得到新數組sortTemp = [[2,3], [5,4], [6,4], [6,7]]
判斷條件爲sortTemp[i][0] > sortTemp[j][0] && sortTemp[i][1] > sortTemp[j][1]
即:寬帶和高度必須同時大於才能滿足


| 編碼 – 套娃信封

import java.util.Arrays;

/**
 * @author yanghao
 * @version LeetCode354Test.java, v 0.1 2019-11-19 11:27
 */
public class LeetCode354Test {

    public static void main(String[] args) {
        LeetCode354Test test = new LeetCode354Test();

        int[][] envelopes = new int[4][2];
        envelopes[0] = new int[]{5, 4};
        envelopes[1] = new int[]{6, 4};
        envelopes[2] = new int[]{6, 7};
        envelopes[3] = new int[]{2, 3};
        
        System.out.println("result == " + test.leetCode354(envelopes));

    }

    public int leetCode354(int[][] envelopes) {
        int y = envelopes.length;

        if(envelopes == null || y == 0){
            return 0;
        }

        if(y == 1){
            return 1;
        }

        //1.先按寬度排序
        int[][] sortTemp = sortTemp(envelopes);
        
        //2.計算最大個數
        int max = getMax(sortTemp);

        return max;
    }

	/**
     * 按寬度排序
     * @param envelopes
     * @return
     */
    private int[][] sortTemp(int[][] envelopes) {
        int y = envelopes.length;

        //新定義一個容器
        int[][] sortTemp = new int[y][2];
        sortTemp[0] = envelopes[0];

        int j = 1;
        for(int i = 1; i < y; i ++){

            j = i;
            while (j >= 1){

                if(envelopes[i][0] < sortTemp[j - 1][0]){
                    sortTemp[j] = sortTemp[j - 1];
                    sortTemp[j - 1] = envelopes[i];
                    j --;

                }else{
                    sortTemp[j] = envelopes[i];
                    j = 0;

                }

            }

        }

        printResult("sortTemp", sortTemp);
        return sortTemp;
    }

	/**
     * 計算最大長度
     * @param sortTemp
     * @return
     */
    private int getMax(int[][] sortTemp) {
        int y = sortTemp.length;
        int max = 1;

        int[] dp = new int[y];
        Arrays.fill(dp, 1);

        for(int i = 0; i < y; i ++){
            for(int j = 0; j < i; j ++){
                if(sortTemp[i][0] > sortTemp[j][0] && sortTemp[i][1] > sortTemp[j][1]){
                    //
                    dp[i] = Math.max(dp[j] + 1, dp[i]);
                }
            }

            max = Math.max(max, dp[i]);
        }

        return max;

    }

    private void printResult(String tempStr, int[][] temps) {

        System.out.println("===== " + tempStr + " ===== ");

        for(int[] temp : temps){
            System.out.println(temp[0] + " " + temp[1]);
        }

    }

}


| 總結

有趣的套娃算法,如果能get到點,應該還是很好理解的,當然了,鼓勵大家能夠多想一些更好的其他解決方案
(案例選取leetcode300題和354題)

有興趣的小夥伴們,可以研究一下三維套娃,嘿嘿😁~

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