lc算法问题

89 阅读4分钟

问题2:

题意描述

给定一个两个有序的数组,长度分别为 mmnn ,设计一个时间复杂度为 O(log(m+n))O(log(m+n)) 的分治算法,找出这 m+nm+n 个数的中位数。

思路

iA 中的下标,而 jB 中的下标:A[i]A 中第一个大于等于中位数 x 的元素;同时 B[j]B 中第一个大于等于中位数 x 的元素。当 A 中元素全部小于 x 时,i = m;同理,当 B 中元素全部小于 x 时,j = n

这意味着,A 中小于 x 的元素数量为 iB 中小于 x 的元素数量为 jA 中大于等于 x 的元素数量为 m - iB 中大于等于 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] 分别是 AB 两个序列中第一个大于等于中位数 x 的元素。因此有:

  1. A[i - 1] < x <= A[i]
  2. A[i - 1] < x <= B[j]
  3. B[j - 1] < x <= A[i]
  4. 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])

复杂度 O(log(m+n))O(log(m+n))

代码 (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_l
  • left_l > right_l
  • left_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]](也就是左上角点和右下角点)。

复杂度 O(mn+n)O(m\sqrt n+n) nn为建筑的左右点范围,mm 为建筑个数

代码
#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);
    }
}