信封嵌套问题
问题描述:给n个信封的长度和宽度。如果信封A的长和宽都小于信封B,那么信封A可以放到信封B里,请求出信封最多可以嵌套多少层。
示例1
输入:[[3,4],[2,3],[4,5],[1,3],[2,2],[3,6],[1,2],[3,2],[2,4]]
返回值:4
说明:从里到外分别是{1,2},{2,3},{3,4},{4,5}。
示例2
输入:[[1,4],[4,1]]
返回值:1
方法一
思路分析
首先对所给的信封进行排序,我们需要按照信封的长度length进行升序排序,如果两个信封的长度相同则可以按照其宽度width进行降序排序,之后对宽度寻找最长递增序列,在寻找最长递增序列时使用动态规划的办法。这里需要解释为什么在长度相同的时候要按照其宽度进行降序排序,因为长度相同的信封无法进行嵌套,因此长度相同的信封只能选取一个,如果按照升序排序,那么就会造成无法嵌套但仍归纳为递增序列中造成错误。
动态规划构造最长自增序列的过程以及转移方程:
- 设置$dp[i]$表示第i个位置上的最长递增序列长度,那么初始化所有位置$dp[i]=1$只有一个元素
- 寻找$i$位置上的最长递增序列,需要寻找该位置之前左侧所有的元素,如果是递增的,那么$dp[i]=max(dp[i],dp[j]+1)$,其中$j<i$
- 最后返回$dp$数组中的最大值即为最长递增序列的长度也即信封嵌套的层数
图解
C++核心代码
class Solution { public: /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param letters intvector<vector<>> * @return int */ static bool cmp(const vector<int> a,const vector<int> b) { if(a[0]==b[0]) return a[1]>=b[1];//长度相同,宽度按照降序排序 else return a[0]<b[0];//长度按照升序排序 } int maxLetters(vector<vector<int> >& letters) { // write code here if(letters.empty()) return 0; int n=letters.size(); sort(letters.begin(),letters.end(),cmp);//对数组元素排序 vector<int > dp(n,0);//设置最长递增序列的数组 dp[0]=1; for(int i=1;i<n;i++) { for(int j=0;j<i;j++)//从i位置之前的位置寻找可能的最长递增序列 { if(letters[i][1]>=letters[j][1]) { dp[i]=max(dp[i],dp[j]+1);//动态规划的办法寻找每个位置的最长子序列数组 } } } return *max_element(dp.begin(),dp.end()); } };
复杂度分析
- 时间复杂度:两层循环,循环次数为$1+2+3+4+n-1=n*(n-1)/2$,因此时间复杂度为$O(n^2)$
- 空间复杂度:借助于一个辅助数组用于存放每个位置的最长递增序列长度,因此空间复杂度为$O(n)$
方法二
思路分析
上面的方法时间复杂度高不符合规定的要求,为提高速度,采取以空间换时间的方式,使用二分法的办法优化动态规划,在求解$dp[i]$的过程中使用二分法的办法优化。先设置一个长度为n的数组ends,初始时ends[0]=letters[0][1],其他位置上的值为0,$ends[b]=c$表示遍历到目前为止,在所有长度为b+1的递增序列中,最小的结尾数是c。每次ends数组存储当前位置之前所有长度为b+1的递增序列中,最小的结尾数是c的元素
图解
C++ 核心代码
class Solution { public: /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * * @param letters intvector<vector<>> * @return int */ static bool cmp(const vector<int> a,const vector<int> b) { if(a[0]==b[0]) return a[1]>=b[1];//长度相同,宽度按照降序排序 else return a[0]<b[0];//长度按照升序排序 } int maxLetters(vector<vector<int> >& letters) { // write code here if(letters.empty()) return 0; int n=letters.size(); sort(letters.begin(),letters.end(),cmp);//对数组元素排序 vector<int > dp(n,1);//设置最长递增序列的数组 dp[0]=1; vector<int> ends(n); ends[0] = letters[0][1];//ends[b]=c,则表示遍历到目前为止,在所有长度为b+1的递增序列中,最小的结尾数是c int right = 0; int ll = 0; int rr = 0; int mm = 0; for (int i = 1; i < n; ++i) { ll = 0; rr = right; while (ll <= rr) { mm = (ll + rr) / 2; if (letters[i][1] > ends[mm]) { ll = mm + 1; } else { rr = mm - 1; } } right = max(right, ll); ends[ll] = letters[i][1]; dp[i] = ll + 1; } return *max_element(dp.begin(),dp.end());//返回最长递增序列的长度 } };
复杂度分析
- 时间复杂度:循环遍历一次,每次查找为二分查找,因此时间复杂度为
- 空间复杂度:借助于两个数组,因此空间复杂度为$O(n)$