问题2:
题意描述
给定一个两个有序的数组,长度分别为 和 ,设计一个时间复杂度为 的分治算法,找出这 个数的中位数。
思路
设 i 是 A 中的下标,而 j 是 B 中的下标:A[i] 是 A 中第一个大于等于中位数 x 的元素;同时 B[j] 是 B 中第一个大于等于中位数 x 的元素。当 A 中元素全部小于 x 时,i = m;同理,当 B 中元素全部小于 x 时,j = n。
这意味着,A 中小于 x 的元素数量为 i,B 中小于 x 的元素数量为 j;A 中大于等于 x 的元素数量为 m - i,B 中大于等于 x 的元素数量为 n - j。满足条件:
- 当
m + n是偶数:i + j = (m - i) + (n - j),此时j = (m + n) / 2 - i。 - 当
m + n是奇数:i + j = (m - i) + (n - j) - 1,此时j = (m + n - 1) / 2 - i = (m + n) / 2 - i(考虑整数除法除不尽时向零取整)。
所以 j 有统一的表达式 (m + n) / 2 - i。这样一来,我们就建立了两个序列下标之间的对应关系,从而将两个序列的问题变为了 1 个序列的问题。
考虑 x 是中位数,而 A[i] 和 B[j] 分别是 A 和 B 两个序列中第一个大于等于中位数 x 的元素。因此有:
A[i - 1] < x <= A[i];A[i - 1] < x <= B[j];B[j - 1] < x <= A[i];B[j - 1] < x <= B[j]。
其中 (1) 和 (4) 是 trivial 的。考虑 (2) 和 (3),即得到目标「条件」:(i == 0 or A[i - 1] < x <= B[j]) and (i == m or B[j - 1] < x <= A[i])。
复杂度
代码 (c++)
static const auto io_sync_off = []() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
return nullptr;
} ();
class Solution {
public:
double findMedianSortedArrays(const std::vector<int>& A, const std::vector<int>& B) {
if (A.empty()) {
return findMedianInSortedArray(B);
} else if (B.empty()) {
return findMedianInSortedArray(A);
} else {
return bsearchWrapper(A, B);
}
}
private:
inline double findMedianInSortedArray(const std::vector<int>& v) {
size_t len = v.size();
if (len % 2 == 0) {
return static_cast<double>(v[len / 2] + v[len / 2 - 1]) / 2.0;
} else {
return v[len / 2];
}
}
inline double bsearchWrapper(const std::vector<int>& A, const std::vector<int>& B) {
const size_t m = A.size(), n = B.size();
if (m <= n) {
return bsearchHelper(A, B);
} else {
return bsearchHelper(B, A);
}
}
inline double bsearchHelper(const std::vector<int>& A, const std::vector<int>& B) {
const size_t m = A.size(), n = B.size();
size_t left = 0, right = m + 1;
size_t i, j;
while (left < right) {
i = left + (right - left) / 2;
j = (m + n) / 2 - i;
if (not(i == m or B[j - 1] < A[i])) {
left = i + 1;
} else if (not(i == 0 or A[i - 1] < B[j])) {
right = i;
} else {
break;
}
}
if ((m + n) % 2 == 0) {
double mx = (i == m) ? B[j] : ((j == n) ? A[i] : std::min(A[i], B[j]));
double mn = (i == 0) ? B[j - 1] : ((j == 0) ? A[i - 1] : std::max(A[i - 1], B[j - 1]));
return (mn + mx) / 2.0;
} else {
return (i == m) ? B[j] : ((j == n) ? A[i] : std::min(A[i], B[j]));
}
}
};
问题3
题意描述
经典的天际线问题
思路
可以用优先队列直接维护一个最大堆来做。
下面讲分治的思路
需要考虑两个建筑物的合并过程,主要分成三种情况:
left_l < right_lleft_l > right_lleft_l = right_l
第一种情况需要添加的点的坐标就是[left_l, max(left_h, rh)] 其中left_h表示当前左边建筑物的高度。
第二种需要添加的点的坐标就是[right_l, max(right_h, lh)],其中right_h表示当前右边建筑物的高度。
第三种需要添加的点的坐标就是[left_l, max(right_h, left_h)]。
最后需要思考边界问题,当建筑物的数量是0的时候,直接返回空数组;当建筑物的数量是1的时候,直接返回[[buildings[0][0], buildings[0][2]], [buildings[0][1], 0]](也就是左上角点和右下角点)。
复杂度 为建筑的左右点范围, 为建筑个数
代码
#include <cmath>
#include <iostream>
using namespace std;
const int N = 1e4 + 10, M = 500;
int n, m;
int w[N], bel[N];
int len;
int height[M];
struct Query {
int l, r, h;
} q[N];
void modify(int l, int r, int h) {
int bl = bel[l], br = bel[r];
if (bl == br)
for (int i = l; i <= r; i++) w[i] = max(w[i], h);
else {
int i = l, j = r;
while (bel[i] == bl) w[i] = max(w[i], h), i++;
while (bel[j] == br) w[j] = max(w[j], h), j--;
for (int k = bel[i]; k <= bel[j]; k++) height[k] = max(height[k], h);
}
}
int main() {
int l, r, h;
while (cin >> l >> h >> r) {
q[++m] = {l, r - 1, h};
n = max(n, q[m].r);
}
n++;
len = sqrt(n);
for (int i = 1; i <= n; i++) bel[i] = i / len;
for (int i = 1; i <= m; i++) modify(q[i].l, q[i].r, q[i].h);
int y = 0;
for (int i = 1; i <= n; i++)
if (max(w[i], height[bel[i]]) != y) {
y = max(w[i], height[bel[i]]);
printf("%d %d ", i, y);
}
}