LeetCode-354.Russian Doll Envelopes及最长上升子序列问题总结
- 最长上升子序列普通dp法
- 最长上升子序列解的打印
- 最长上升子序列NlogN法
- LeetCode354-Russian Doll Envelopes题解
最长上升子序列普通dp法
生成长度为N的数组dp,dp[i]表示的是在以arr[i]这个数结尾的情况下,arr[0…i]中的最长递增子序列长度。
- 对第一个数arr[0]来说,dp[0] = 1,最长递增子序列就是自己。
- 当计算到dp[i]的时候,最长递增子序列要以arr[i]结尾,所以我们在arr[0…i-1]中所有比arr[i]小的数可以作为最长递增子序列的倒数第二个数,这些数中,哪个的最长递增子序列更大,就选择哪个。即dp[i] = max(dp[j] + 1) ,0 <= j < i,arr[j] < arr[i]。
/**
* 获得最长递增子序列的长度
* dp[i]表示以arr[0]结尾的情况下,arr[0...i]中的最大递增子序列
*/
public static int lengthOfLIS(int[] arr){
if(arr == null || arr.length == 0)return 0;
int[] dp = new int[arr.length];
int res = 1;
for(int i = 0; i < arr.length; i++){
dp[i] = 1;
for(int j = 0; j < i; j++){
if(arr[j] < arr[i]){
dp[i] = Math.max(dp[i],dp[j]+1);
}
res = Math.max(res,dp[i]);
}
}
return res;
}
最长上升子序列解的打印
根据上面的方法可以求得dp数组,我们根据dp数组就可以得到最长上升子序列的解。
- 从dp数组中的最大值dp[maxi]表示的是以arr[maxi]结尾的,而且是最长的上升子序列;
- 我们从maxi往前面找,如果前面的某个dp[i],满足arr[i] < arr[maxi] 且dp[maxi] = dp[i] + 1,就说明这个是我们找最长递增子序列时候取的值,可以作为最长递增子序列的倒数第二个数。
- 然后依次往前找,可以得到解。
public static int[] getLis(int[] arr,int[] dp){
int len = 0;
int index = 0;
for(int i = 0; i < dp.length; i++){
if(dp[i] > len){
len = dp[i];
index = i;
}
}
int[] lis = new int[len];
lis[--len] = arr[index];
for(int i = index - 1; i >= 0; i--){
if(dp[i] == dp[index] - 1 && arr[i] < arr[index]){
lis[--len] = arr[i];
index = i;
}
}
return lis;
}
最长上升子序列NlogN法
这个方法是使用一个额外的数组ends[],dp[i]记录的还是以arr[i]结尾的最长递增子序列,ends[i]记录的是在所有长度为i+1的递增序列中,最小的结尾数是ends[i]。然后使用二分的方式在ends数组中查找,具体过程如下 :
- dp[0] = 1,ends[0] = arr[0]表示的是,在所有长度为1的递增序列中,最小结尾是arr[0],此时只有一个数;
- 用一个right变量记录ends数组的有效范围;
- 遍历到某个数arr[i]的时候,在ends[]数组中二分查找最左边的>arr[i]的数(二分查找看这篇博客),如果有某个位置l,说明以这个数结尾的最长递增子序列是l + 1,而且此时找到更小的结尾,所以ends[l]要改写成arr[i];如果没有这个位置,说明ends数组中的数都比这个小,所以此时有效区(right)的范围+1,且ends[l] 补到ends数组的后面,dp[i] = l + 1;
- 一直遍历整个数组,最后的最长长度就是有效区的长度+1(right + 1);
/**
* ends数组中 ends[i]表示的是长度为i+1的最长递增子序列中最小结尾是什么
*/
public static int lengthOfLIS2(int[] arr){
if(arr == null || arr.length == 0)return 0;
int[] dp = new int[arr.length];
int[] ends = new int[arr.length];
dp[0] = 1;
ends[0] = arr[0];
int right = 0;//右边界
int l = 0,m = 0,r = 0;
for(int i = 1; i < arr.length; i++){
l = 0;
r = right;
while(l <= r){
m = l + (r-l)/2;
if(arr[i] > ends[m]){
l = m+1;
}else {
r = m-1;
}
}
right = Math.max(right,l);
ends[l] = arr[i];
dp[i] = l + 1;
}
return right + 1;
}
LeetCode354-Russian Doll Envelopes题解
题目链接
题目
给定一些标记了宽度和高度的信封,宽度和高度以整数对形式 (w, h) 出现。当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。请计算最多能有多少个信封能组成一组“俄罗斯套娃”信封(即可以把一个信封放到另一个信封里面)。例如 :
输入: envelopes = [[5,4],[6,4],[6,7],[2,3]]
输出: 3
解释: 最多信封的个数为 3, 组合为: [2,3] => [5,4] => [6,7]。
解析
求解过程:先按a从小到大进行排序,当a相同时,按b从大到小排序。然后求解b的最长递增子序列。
为什么b要按从大到小排列呢?按照最长递增子序列的O(N*logN)方法,当前数arr[i]大于ends数组中所有的数(末尾的最大),我们会将arr[i]添加在ends数组中;否则在ends数组中二分查找第一个大于当前数的数且替换它。所以我们的做法会保证在a相等的情况下,b可以有一个最小值,这样可以摞相对多的数。以达更长的序列,同时也避免了a相同b不相同时摞在一起的情况。
import java.util.Arrays;
import java.util.Comparator;
/**
* 俄罗斯套娃信封问题
* 题目链接 : https://leetcode-cn.com/problems/russian-doll-envelopes/description/
*/
public class RussianDollEnvelopes {
private static class Node{
public int a;
public int b;
public Node(int a, int b) {
this.a = a;
this.b = b;
}
}
public static class MyComparator implements Comparator<Node>{
@Override
public int compare(Node o1, Node o2) {
if(o1.a == o2.a){
return o2.b - o1.b;
}else {
return o1.a - o2.a;
}
}
}
/**
* 最长递增子序列的OLogN方法
* @param envelopes
* @return
*/
public static int maxEnvelopes(int[][] envelopes) {
if(envelopes == null || envelopes.length == 0 || envelopes[0].length == 0)return 0;
Node[] nodes = new Node[envelopes.length];
for(int i = 0; i < envelopes.length; i++){
nodes[i] = new Node(envelopes[i][0],envelopes[i][1]);
}
Arrays.sort(nodes,0,nodes.length,new MyComparator());
int[] ends = new int[envelopes.length];
ends[0] = nodes[0].b;
int right = 0;
int l = 0,m = 0,r = 0;
int res = 1;
for(int i = 1; i < nodes.length; i++){
l = 0;
r = right;
while(l <= r){
m = l + (r-l)/2;
if(nodes[i].b > ends[m]){
l = m + 1;
}else {
r = m - 1;
}
}
right = Math.max(l,right);
ends[l] = nodes[i].b;
}
return right + 1;
}
public static void main(String[] args) {
int[][] arr = {
{5,4},{6,4},{6,7},{2,3}
};
System.out.println(maxEnvelopes(arr));
}
}