算法设计与分析第六周作业
这周因为上课时说到了二分查找算法,觉得自己印象不是特别深,所以就选了一道二分查找的算法题来分析求解。
题目链接 -> 二分查找之Russian Doll Envelopes
题目详情
- 题目大意:给定一堆信封,其中它们的宽和高都已知,设计一个算法,计算出能够互相嵌套的信封的最大个数,其中,一个信封能嵌套另一个信封的前提是宽和高的值都比后者的大。特别要注意的是,信封只能以相同的方向嵌套。
- 样例分析:
输入 输出 分析 [ [5, 4], [6, 4], [6, 7], [2, 3] ] 3 从小到大的嵌套信封为[2, 3] -> [5, 4] -> [6, 7],故最大的可以嵌套的信封数量为3 [[2, 100], [3, 200], [4, 300], [5, 500], [5, 400], [5, 250], [6, 370], [6, 360], [7, 380]] 5 从小到大的嵌套信封为[2, 100] -> [3, 200] -> [5,250] -> [6, 360] -> [7, 380],故最大的可以嵌套的信封数量为5
题目分析与算法设计
根据题目我们可以知道,我们优先查看信封的宽度,对于当前信封,那些具有比它宽度小的且高度也小的信封是能够被当前的信封嵌套的,此时当前信封能够嵌套的信封个数加一,对于那些宽度相等的但是高度不一样的信封,只要找出能够被当前信信封装下的有最小的高的信封。
具体算法如下:
- 按照信封宽度以升序把每个信封排好序,同时信封宽度相同,按照信封高度排序(使用stl库的sort函数);
- 遍历信封,针对当前的信封,如果在它之前的信封的高小于当前信封的高,当前信封可嵌套的信封个数加一;
- 维护一个max值,使得记录当前为止,能找到的可以嵌套的信封的最大数量;
- 遍历完成,即可找到可嵌套信封的最大的值;
代码详情及其复杂度分析
int maxEnvelopes(vector<pair<int, int>>& envelopes) {
if (envelopes.empty()) return 0;
// 使用sort来对数组排序,优先按照宽度排序,若宽度一样时,按照高度排序
sort(envelopes.begin(), envelopes.end());
// 对于每一个信封,计算它能够套用的信封的个数
int len = envelopes.size();
int *ans = new int[len];
int max = 0;
for (int i = 0; i < len; i ++) {
ans[i] = 1; // 包含自己
for (int j = 0; j < i; j ++) {
if (envelopes[i].first > envelopes[j].first && envelopes[i].second > envelopes[j].second) {
ans[i] = (ans[i] > ans[j] + 1) ? ans[i] : ans[j] + 1;
}
}
// 每次遍历都更新一次max
max = (max > ans[i]) ? max : ans[i];
}
delete[] ans;
return max;
}
有两个循环,外循环遍历了所有的信封,内循环遍历当前访问的信封之前的所有信封,因此时间复杂度为O(n^2)。
算法优化
上面的算法对于每个信封,都求出了它能嵌套的信封的个数,这样得到结果的效率很明显是比较低的。我们不妨换个角度看问题:直接使用一个数组来记录符合嵌套条件的最小的高,当遍历了所有的信封,得到的数组的长度就是能嵌套的信封的最大个数了。而因为在宽度相同的情况下,我们的目标是找出能够嵌套的最小高度的信封,因为我们可以使用二分法针对信封高度对该题目进行优化:
- 对所有信封按照宽度以升序排序,宽度相同时以高度降序排序(使用sort,自己构造比较函数来规定排序规则);
- 维护一个数组ans,用来记录嵌套外层的最小的高;
- 把高度看成一个集合,把它们分成两部分考虑,维护整型 left 和 right,分别表示当前要查找的区间的左、右端点;
- 如果ans在区间中点处的值小于当前访问的信封的高,那么说明高度的值还能继续减小,故继续考虑区间的右边部分,否则考虑区间的左半部分,直到找到最小的高;
- 加入满足条件的最小高,更新ans;
代码详情如下:
int maxEnvelopes(vector<pair<int, int>>& envelopes) {
if (envelopes.empty()) return 0;
// 使用sort来对数组排序,优先按照宽度排序
sort(envelopes.begin(), envelopes.end(), cmp);
vector<int> ans; // 记录当前所能嵌套的信封的最小高
int len = envelopes.size();
for (int i = 0; i < len; i ++) {
int left = 0;
int right = ans.size();
int tmp = envelopes[i].second;
// 对高进行二分查找,找到的最小的高满足能嵌套当前信封
while (left < right) {
int mid = left + (right - left) / 2;
// 把结果分成两部分,如过中值小于当前的信封的高,则在右半部分继续查找,否则在左半部分查找
if (ans[mid] < tmp) left = mid + 1;
else right = mid;
}
// 修改能够符合嵌套的最小高
if (right >= ans.size()) ans.push_back(tmp);
else ans[right] = tmp;
}
return ans.size();
}
static bool cmp(pair<int, int> &a, pair<int, int> &b) {
// 当宽相等时,比较高,高大的在前面
if (a.first == b.first) return a.second > b.second;
return a.first < b.first;
}
上述算法在遍历所有的信封的同时,对信封的高进二分查找,每进行一次二分查找,查找的个数就将为原来的一半,因此,算法总的时间复杂度为O(n*log(n)),显然,在时间复杂度上要比之前的算法好很多。
谢谢阅读。
参考资料
- https://blog.csdn.net/liuchonge/article/details/78404561
- http://www.cnblogs.com/grandyang/p/5568818.html