俄罗斯套娃?一维二维“大鱼吃小鱼”算法

| 题引

俄罗斯套娃是俄罗斯特产的木制玩具,一般由多个一样图案的空心木娃娃一个套一个组成,最多可达十多个,通常为圆柱形,底部平坦可以直立。颜色有红色,蓝色,绿色,紫色等。最普通的图案是一个穿着俄罗斯民族服装的姑娘,叫做“玛特罗什卡”,这也成为这种娃娃的通称(如下图所示)。
在这里插入图片描述


| 一维套娃 - 最长上升子序列

正题
给定一个无序的整数数组,找到其中最长上升子序列的长度
如:

输入: [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题)

有兴趣的小伙伴们,可以研究一下三维套娃,嘿嘿😁~

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