| 题引
俄罗斯套娃是俄罗斯特产的木制玩具,一般由多个一样图案的空心木娃娃一个套一个组成,最多可达十多个,通常为圆柱形,底部平坦可以直立。颜色有红色,蓝色,绿色,紫色等。最普通的图案是一个穿着俄罗斯民族服装的姑娘,叫做“玛特罗什卡”,这也成为这种娃娃的通称(如下图所示)。
| 一维套娃 - 最长上升子序列
正题
给定一个无序的整数数组,找到其中最长上升子序列的长度
如:
输入: [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]。
| 要点
一维套娃
- 顺序不能改变
- 可能会有多个上升子序列,需要找到最长的那一个
二维套娃
- 顺序可以改变
- 不能旋转
- 宽度和长度必须同时是上升子序列
| 分析 - 最长上升子序列
一维套娃
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题)
有兴趣的小伙伴们,可以研究一下三维套娃,嘿嘿😁~