小彩笔的痛苦刷题日记 —— 俄罗斯套娃信封问题

俄罗斯套娃信信封问题
  给定一些标记了宽度和高度的信封,宽度和高度以 整数对 的形式(w, h)出现。当另一个信奉的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里吗,如同俄罗斯套娃。 请计算最多能有多少个信奉能组成一组“俄罗斯套娃”信封(不允许旋转信封)

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

解析:
  该问题需要先按特定的规则进行排序,排序后就转换成为一个最长递增子序列问题
可以先看下面这个题目:
最长递增子序列:
  给一个整数数组nums,找到其中最长严格递增子序列长度。子序列是由数组派生而来的的序列,删除(或不删除)数组中的元素而不改变其余元素顺序。例如,[3,6,2,7]是数组[0,3,1,6,2,2,7]的子序列。

  • 注意题中所说的是【子序列】和【子串】是不一样的,子串一定是连续的,但子序列不一定是连续的

动态规划解法:
  动态规划的核心即数学归纳法,主要思路就是:若想证明一个结论是否成立,先假设这个结论在k<n是成立,然后根据这个假设,想办法证明k=n时结论也成立。如果能够证明出来,那么说明这个结论对于k等于任何数都成立。类似的在设计动态规划,需要一个dp数组,假设dp[0,..,i-1]已经计算出来,如何通过这些结果计算dp[i]

  • 在此题中,定义:dp[i]表示以nums[i]这个数结尾的最长递增子序列的长度

  根据这个定义,dp[i]的初始值至少要为1,具体过程如下:
               在这里插入图片描述
  根据这个定义,子序列的最大长度应该是dp数组中的最大值

int ret = 0;
for(int i = 0; i < dp.length; i++){
	ret = Math.max(dp[0], dp[dp.length - 1]);
}
return ret;

  这个dp数组是我们用眼睛看出来的,如何用逻辑实现计算每个dp[i]?使用数学归纳思想:假设我们已经知道了dp[0,..,4]的所有结果,如何推出dp[5]
            在这里插入图片描述
  根据之前对dp数组的定义,dp[5]的含义就是以nums[5]为结尾的最长递增子序列的长度
  此处nums[5] = 3,又因为要找的是递增子序列,只需要找到前面那些结尾比3小的子序列,然后把3接到这个子序列的后面,就得到一个新的递增的序列,且这个新子序列的长度加一
在这里插入图片描述

for(int j = 0; j < i; j++){
	if(nums[i] > nums[j]){
		dp[i] = Math.max(dp[i], dp[j]+1)}
}
i=5这个位置,j遍历nums数组,
因为是要得到递增的子序列,我们用nums[i]这个位置的值与nums[j]这个位置的值进行比较
小了,那就不是该放入递增子序列的数,j++
大了,就是递增子序列,举几个例子:
 j = 0, i = 5; nums[i] > nums[j]; dp[5] = Math.max{d[5], dp[0] + 1} = Math.max{0, 1 + 1} = 2
 j = 4, i = 5; nums[i] > nums[j]; dp[5] = Math.max{d[5], dp[4] + 1} = Math.max{2, 2 + 1} = 2

  求出了dp[5]那么前面的类似于数学归纳法也可以计算

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);
		}
	}
}

  完整代码如下:

public int lengthOfLIS(int[] nums) {
    int[] dp = new int[nums.length];
    // base case:dp 数组全都初始化为 1
    Arrays.fill(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;
}
  • 总结如何找到动态规划的状态转移关系:
    1、明确dp数组所存数据的含义。
    2、根据dp数组的定义,运用数学归纳法的思想,假设dp[0,...,i-1]都已知,想办法求出dp[i],如果无法求出,可能是dp数组定义不够恰当或dp数组存储的信息还不够,将其扩大到二维甚至三维。
    此题还有二分查找解法,因为俄罗斯套娃信封问题用不到,所以不做说明。

现在回到信封嵌套问题:

      在这里插入图片描述
  这其实是最长递增子序列(LIS)的一个变种,很明显,每次合法的嵌套是大的套小的,相当于找一个最长递增子序列,其长度就是最多能嵌套的信封个数,但问题在于,此处的信封是二维的。

解法:

  现对宽度w进行升序排列,如果遇到w相同的情况,则按照高度h降序排序。之后把所有的h作为一个数组,在这个数组上计算LIS的长度就是答案。

  • 现队这些数对按宽度进行升序排序

           在这里插入图片描述

  • 然后再h上寻找最长递增子序列:
                在这里插入图片描述
  • 对于宽度w相同的数对,对其高度h进行降序排序。因为两个宽度相同的信封不能互相包含,逆序排序保证在w相同的数对中最多只选取一个。
// envelopes = [[w, h], [w, h], ...]
public int maxEnvelopes(int[][] envelopes){
	int n = envelopes.length;
	// 按宽度升序排列,如果宽度一样,则按高度降序排列
	Arrays.sort(envelopes, new Comparator<int[]>()
	{
		@Override
     public int compare(int[] a, int[] b) {
		//a[i]里面的i指的是按照每一行的第i列进行排序
		// 所以a[0]就是把按照第0列的那个数对所有行进行排序,b[0]既代表其他行的数字
		//     a[1]就是按照第一列进行排序。
         if(a[0]==b[0]){
             return b[1]-a[1]; //如果第 0列的数字相等,那么按第 1列的降序
         }else{
             return a[0]-b[0]; //如果第 0列的数字不等,那么按第 0列的升序
         }
     }
	});
	
	// 对高度数组寻找LIS
	int[] height = new int[n];
	for(int i = 0; i< n; i++){
		height[i] = envelopes[i][1];  //将高度这一部分放进一个新的数组
	}
	return lengthOfLIS(height);
}
  • 是用动态规划设计最长递增子序列
public int lengthOfLIS(int[] nums) {
    int[] dp = new int[nums.length];
    // base case:dp 数组全都初始化为 1
    Arrays.fill(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;
}

该题目解析均来自 —— labuladong

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值