leetcode:4. 寻找两个正序数组的中位数

题目

给定两个大小分别为 mn 的正序(从小到大)数组 nums1nums2。请你找出并返回这两个正序数组的 中位数
算法的时间复杂度应该为 **O(log (m+n))**

示例:

  1. 输入:nums1 = [1,3], nums2 = [2]
  2. 输出:2.00000
  3. 解释:合并数组 = [1,2,3] ,中位数 2
  1. 输入:nums1 = [1,2], nums2 = [3,4]
  2. 输出:2.50000
  3. 解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

解答 & 代码

题目要求时间复杂度应该为 **O(log (m+n))** ,因此只能用二分查找

要寻找两个正序数组的中位数,

  • 如果两个数组的总长度 len 为偶数,那么要找到第 len/2 小的元素和第 len/2+1 小的元素,求平均;
  • 如果两个数组的总长度 len 为奇数,那么要找到第 len/2+1 小的元素。

因此问题转换为:寻找两个正序数组中第 k 小的元素

  1. class Solution {
  2. private:
  3. // 寻找两个正序数组 nums1、nums2 中第 k 小的数,并返回
  4. int getKthElement(vector<int>& nums1, vector<int>& nums2, int k)
  5. {
  6. int len1 = nums1.size(); // 数组 1 的长度
  7. int len2 = nums2.size(); // 数组 2 的长度
  8. int left1 = 0; // nums1 数组中有效元素的左边界,left1 左边的元素都不可能是第 k 小的元素
  9. int left2 = 0; // nums2 数组中有效元素的左边界,left2 左边的元素都不可能是第 k 小的元素
  10. while(true)
  11. {
  12. /* 边界情况 */
  13. // 边界情况 1:如果一个数组的有效部分为空,直接返回另一个数组中第 k 小的元素
  14. if(left1 == len1)
  15. return nums2[left2 + k - 1];
  16. if(left2 == len2)
  17. return nums1[left1 + k - 1];
  18. // 边界情况 2:如果 k == 1,返回两个数组首元素的最小值
  19. if(k == 1)
  20. return min(nums1[left1], nums2[left2]);
  21. /* 正常情况 */
  22. // 分别取两个数组有效部分第 k/2 小的元素下标 idx1、idx2
  23. // 如果idx1 or idx2 越界,则取对应数组最后一个元素下标
  24. int idx1 = min(len1 - 1, left1 + k / 2 - 1);
  25. int idx2 = min(len2 - 1, left2 + k / 2 - 1);
  26. // 如果 nums1[idx1] <= nums2[idx2],那么 idx1 前面最多有 2*(k/2 - 1) 个元素比它小
  27. // 即 nums1[idx1] 最多只能是第 k-1 小的元素,因此 nums1[0..idx1] 部分都是无效的,删除
  28. if(nums1[idx1] < nums2[idx2])
  29. {
  30. k -= idx1 - left1 + 1; // 更新 k 值,减去 nums1 删除/无效的元素个数
  31. left1 = idx1 + 1; // 更新 left1 范围
  32. }
  33. // nums2[0..idx2] 的元素都不可能是第 k 小的元素,全部删除,缩小范围
  34. else
  35. {
  36. k -= idx2 - left2 + 1; // 更新 k 值,减去 nums2 删除/无效的元素个数
  37. left2 = idx2 + 1; // 更新 left2 范围
  38. }
  39. }
  40. }
  41. public:
  42. double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
  43. int len = nums1.size() + nums2.size(); // 两个数组总长度
  44. // 如果两个数组总长度 len 为偶数
  45. // 需要找到第 len/2 小的元素和第 len/2+1 小的元素,求平均
  46. if(len % 2 == 0)
  47. {
  48. int mid1 = getKthElement(nums1, nums2, len / 2);
  49. int mid2 = getKthElement(nums1, nums2, len / 2 + 1);
  50. return (mid1 + mid2) / 2.0;
  51. }
  52. // 如果两个数组总长度 len 为奇数:需要找到第 len/2+1 小的元素
  53. else
  54. return getKthElement(nums1, nums2, len / 2 + 1);
  55. }
  56. };

复杂度分析:设两个数组长度分别为 M、N

  • 时间复杂度 O(log(M + N)):初始时 K = (M + N) / 2 or K = (M + N) / 2 + 1。每轮循环可以将查找范围减少 K/2 个元素(每轮循环 K 也会减小 2/K,即删除的无效元素个数),因此时间复杂度 O(log K) = O(log (M + N))
  • 空间复杂度 O(1)

执行结果:

  1. 执行结果:通过
  2. 执行用时:24 ms, 在所有 C++ 提交中击败了 78.36% 的用户
  3. 内存消耗:86.8 MB, 在所有 C++ 提交中击败了 88.28% 的用户