俄罗斯套娃信封问题

题目

给定一些标记了宽度和高度的信封,宽度和高度以整数对形式 (w, h) 出现。当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。

请计算最多能有多少个信封能组成一组“俄罗斯套娃”信封(即可以把一个信封放到另一个信封里面)。

说明: 不允许旋转信封。

示例:

输入: envelopes = [[5,4],[6,4],[6,7],[2,3]]
输出: 3
解释: 最多信封的个数为 3, 组合为: [2,3] => [5,4] => [6,7]。

穷举法

给定个信封,分别取个信封排列。再过泸可以嵌套的信封排列。最后取其中嵌套信封最多的排列。

个元素中取个元素组合排列的数量为

举个例子,给定四个信封[[5,4],[6,4],[6,7],[2,3]]。套娃信封可以是由一个信封、两个信封、三个信封或四个信封组成。逐一罗列从四个信封中取出一个、两个、三个和四个信封的排列。

从四个信封中取出一个信封有四种排列:

从四个信封中取出两个信封有十二种排列:

代码

package io.github.rscai.leetcode.bytedance.dynamic; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class Solution1031A { public int maxEnvelopes(int[][] envelopes) { int envelopeCount = envelopes.length; int max =0; List<int[]> envelopeInList = new ArrayList<>(envelopeCount); for (int index = 0; index < envelopeCount; index++) { envelopeInList.add(envelopes[index]); } for (int n = 1; n <= envelopeCount; n++) { for (List<int[]> permutation : permute(envelopeInList, n)) { if (isValid(permutation) && permutation.size() > max) { max = permutation.size(); } } } return max; } private List<List<int[]>> permute(List<int[]> elements, int n) { List<List<int[]>> permutations = new ArrayList<>(); if (n == 0) { permutations.add(Collections.emptyList()); } for (int index = 0; index < elements.size(); index++) { int[] firstElement = elements.get(index); List<int[]> remainElements = new ArrayList<>(); remainElements.addAll(elements.subList(0, index)); remainElements.addAll(elements.subList(index + 1, elements.size())); for (List<int[]> remainPermutation : permute(remainElements, n - 1)) { List<int[]> permutation = new ArrayList<>(); permutation.add(firstElement); permutation.addAll(remainPermutation); permutations.add(permutation); } } return permutations; } private boolean isValid(List<int[]> permutation) { if (permutation.size() == 1) { return true; } int[] outter = permutation.get(0); for (int index = 1; index < permutation.size(); index++) { int[] inner = permutation.get(index); if (inner[0] >= outter[0] || inner[1] >= outter[1]) { return false; } outter = inner; } return true; } }

首先,依次罗列所有的排列。

debug-A1

然后,过泸出可以嵌套在一起的排列,并求出其中信封数最大值。

debug-A2

排列用递归的方式实现。先将问题拆分为一个「简单的子问题」- 从可选集合𥚃取一个元素。当可选集合大于1时,「一个元素」有多种取值。

debug-A3

除去一个「简单的子问题」后,剩余一个更小的可选元素集合和更小的排列数。

debug-A4

再递归调用自身解决「其余问题」。

debug-A5

递归终止条件为为空。

debug-A6

复杂度分析

时间复杂度

个元素中取个元素组合排列的数量为。本演算法罗列了从1到m的所有排列。所以,时间复杂度为:

空间复杂度

其罗列了所有的排列,空间复杂度为:

贪婪法

贪婪演算法(英语:greedy algorithm),又称贪心演算法,是一种在每一步选择中都採取在目前状态下最好或最佳(即最有利)的选择,从而希望导致结果是最好或最佳的演算法。

假设给定一个信封a,在选择一个信封放入a中时,选择了可容维信封数最多的信封,则将其放入a所组成的「套娃信封」数是最大。

递推公式:

其中,是以为最外层信封组成的「套娃信封」最大数,能容纳的所有信封集合。

代码

package io.github.rscai.leetcode.bytedance.dynamic; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; public class Solution1031B { public int maxEnvelopes(int[][] envelopes) { List<int[]> envelopeInList = new ArrayList<>(envelopes.length); for (int[] envelope : envelopes) { envelopeInList.add(envelope); } Collections.sort(envelopeInList, new Comparator<int[]>() { @Override public int compare(int[] left, int[] right) { if (left[0] < right[0]) { return -1; } else if (left[0] > right[0]) { return 1; } else { if (left[1] < right[1]) { return -1; } else if (left[1] > right[1]) { return 1; } else { return 0; } } } }); int[] maxs = new int[envelopeInList.size()]; for (int index = 0; index < envelopeInList.size(); index++) { int[] envelope = envelopeInList.get(index); int canHoldMax = 0; for (int k = index - 1; k >= 0; k--) { int[] inner = envelopeInList.get(k); if (envelope[0] > inner[0] && envelope[1] > inner[1] && maxs[k] > canHoldMax) { canHoldMax = maxs[k]; } } maxs[index] = canHoldMax + 1; } int max = 0; for (int value : maxs) { if (value > max) { max = value; } } return max; } }

首先,将信封从小到大排序。因为信封的「尺寸」是一个二维向量,所以实际上无法以一维排序。这𥚃按一个近似的序列排序:先比较长,若长相等则比较寛。

debug-B1

然后,从小往大依次计算以每个信封为最外层信封所能组成的最大「套娃信封」。

debug-B2

一个信封所能组成的最大「套娃信封」由其所能容纳的所有信封中「套娃信封」值最大的再加1。因为所有信封都从小到大排序了(虽然是㧒近似的顺序排序的,但保证了右侧的信封绝不会小于左侧信封),所以「可容纳的信封」都在序列左侧。

debug-B3

最后,再遍历一遍,找出最大的「套娃信封」。

debug-B4

复杂度分析

时间复杂度

本演算法使用一次JDK中ArrayList的排序实现,其时间复杂度是。再两层循环遍历所有信封。时间复杂度为:

空间复杂度

使用了尺寸与轮入值相同的maxs存储子问题解。空间复杂度为:

results matching ""

    No results matching ""