【LeetCode刷题记录】4. 寻找两个有序数组的中位数

题目描述:
在这里插入图片描述
一个最简单、最容易想到的方法是:将两个数组排序、存入同一个vector,计算中位数,如:

double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
 int n = nums1.size();
 int m = nums2.size();
 vector<int> nums(n + m);
 //int* nums = new int[n + m];
 if (n == 0) {
  if (m % 2)return nums2[m / 2];
  else return (nums2[m / 2 - 1] + nums2[m / 2]) / 2.0;
 }
 if (m == 0) {
  if (n % 2)return nums1[n / 2];
  else return (nums1[n / 2 - 1] + nums1[n / 2]) / 2.0;
 }
 int count = 0, i = 0, j = 0;
 while (count < (n + m)) {
  if (i == n) {
   while (j < m) {
    nums[count++] = nums2[j++];
   }
   break;
  }
  if (j == m) {
   while (i < n) {
    nums[count++] = nums1[i++];
   }
   break;
  }
  if (nums1[i] < nums2[j])nums[count++] = nums1[i++];
  else nums[count++] = nums2[j++];
 }
 if (count % 2)return nums[count / 2];
 else return (nums[count / 2 - 1] + nums[count / 2]) / 2.0;
}

复杂度分析:遍历两个数组,时间复杂度为O(m+n);创建了一个数组,空间复杂度为O(m+n)。题目要求时间复杂度为O(log(m+n)),显然此方法不符合要求。
关于上述方法,实际上不需要排序、存储两数组,对于m+n为奇数的,只需要知道两数组中第(m+n)/2+1小的元素就可以了;偶数时,知道第(m+n)/2和第(m+n)/2+1个元素就可以了。所以,有:

double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
 int n = nums1.size();
 int m = nums2.size();
 int len = n + m, l = -1, r = -1, idxn = 0, idxm = 0;
 for (int i = 0; i <= len / 2; i++) {
  l = r;
  if (idxn < n && (idxm >= m || nums1[idxn] < nums2[idxm]))
   r = nums1[idxn++];
  else
   r = nums2[idxm++];
 }
 if (len % 2)return r;
 else return (l + r) / 2.0;
}

此时,由于需要遍历前(m+n)/2个元素,时间复杂度为O(m+n);没有创建新的数组,空间复杂度为O(1)。显然,时间复杂度仍不满足题目要求。
题解:
一、二分法
观察题目要求的时间复杂度O(log(m+n)),看到log第一时间想到二分法。
假设我们要找第k小的元素,分别在两数组中找到第k/2个元素,比较当前两元素,小的那个元素所在的数组的前k/2个元素一定不是要找的,故排除;此时,令k=k-k/2,问题转化为,寻找第k(=k-k/2)小的元素,反复应用上面思路,直至k=1,则问题解决。
一个特例,假设其中一个数组的长度小于k/2,只需要将这个数组的最后一个元素拿出来比较就可以了。
奇偶数?奇数时:寻找第(m+n)/2+1小的元素;偶数时,寻找第(m+n)/2和第(m+n)/2+1小的元素。
递归实现

double findMedianSortedArraysDiv(vector<int>& nums1, vector<int>& nums2) {
 int n = nums1.size();
 int m = nums2.size();
 int len = n + m;
 if (len % 2)
  return getKth(nums1, 0, n - 1, nums2, 0, m - 1, len / 2 + 1); //Kth
 else
  return (getKth(nums1, 0, n - 1, nums2, 0, m - 1, len / 2) + getKth(nums1, 0, n - 1, nums2, 0, m - 1, len / 2 + 1)) / 2.0;
}

int getKth(vector<int>& nums1, int s1, int e1, vector<int>& nums2, int s2, int e2, int k) {
 int len1 = e1 - s1 + 1;
 int len2 = e2 - s2 + 1;
 if (len1 > len2)return getKth(nums2, s2, e2, nums1, s1, e1, k);
 if (!len1)return nums2[s2 + k - 1];
 if (k == 1)return nums1[s1] < nums2[s2] ? nums1[s1] : nums2[s2];
 int k1 = len1 < k / 2 ? len1 : k / 2;
 int k2 = len2 < k / 2 ? len2 : k / 2;
 int p1 = s1 + k1 - 1;
 int p2 = s2 + k2 - 1;
 if (nums1[p1] < nums2[p2])
  return getKth(nums1, p1 + 1, e1, nums2, s2, e2, k - k1);
 else
  return getKth(nums1, s1, e1, nums2, p2 + 1, e2, k - k2);
}

复杂度分析:原问题规模为k=(m+n)/2+1,每排除一次,规模减少k/2。故2^x=k,需要x=logk=log(m+n/2)次循环,时间复杂度为O(log(m+n));由于递归为尾递归,编译器没有额外的堆栈操作,空间复杂度为O(1)。
二、“割”
这里引用讨论区解释,具体思路见代码。
将两数组划分为:
在这里插入图片描述
在这里插入图片描述

double findMedianSortedArraysCut(vector<int>& nums1, vector<int>& nums2) {
 int n = nums1.size();
 int m = nums2.size();
 if (n > m) {
  return findMedianSortedArraysCut(nums2, nums1);
 }
 int LMax1, LMax2, RMin1, RMin2, c1, c2, lo = 0, hi = 2 * n;
 while (lo <= hi) {
  c1 = (lo + hi) / 2; //start:c1=n(/2)
  c2 = m + n - c1;  //c2=m(/2),nL == nR
  LMax1 = (c1 == 0) ? INT_MIN : nums1[(c1 - 1) / 2];
  RMin1 = (c1 == 2 * n) ? INT_MAX : nums1[c1 / 2];
  LMax2 = (c2 == 0) ? INT_MIN : nums2[(c2 - 1) / 2];
  RMin2 = (c2 == 2 * m) ? INT_MAX : nums2[c2 / 2];
  if (LMax1 > RMin2)
   hi = c1 - 1;
  else if (LMax2 > RMin1)
   lo = c1 + 1;
  else
   break;
   }
 return (max(LMax1, LMax2) + min(RMin1, RMin2)) / 2.0;
}

复杂度分析:首次查找区间为[0,n],其中n=min(n,m),该区间的长度在每次循环之后都会减少为原来的一半。因此,时间复杂度为O(log(min(n,m)));只需要恒定的内存来存储几个局部变量,因此,空间复杂度为O(1)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值