[Leetcode 4] Median of Two Sorted Arrays

本文介绍了求解两个有序数组中位数的问题,包括相同长度和不同长度的两种情况,并给出了详细的算法分析及代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原题见leetcode 4。这题分简单版(长度相同)、复杂版(长度不同)。leetcode原题是复杂版,这里都会介绍。

求相同长度两个有序数组的中位数

基本思想是二分查找,分别找到两个数组的中位数(O(1)),然后根据它们之间的关系来决定丢弃数组的哪一半。每次丢掉一半,直到找到中位数,或者其中一个数组长度为2(base case,这种情况下需要计算两个长度都为2的数组的中位数,时间复杂度O(1))。总的时间复杂度为log(n)。

设数组A1、A2的中位数分别为m1、m2,合并后的数组A的中位数为m。考虑三种情况:

1. m1 = m2

(1)n为奇数。则m=(m1+m2)/2=m1。

(2)n为偶数(这里采用的中位数定义是中间两位数的算术平均值)。假设A1中间两位是a1, a2,A2中间两位是a3, a4,那么合并后A的中间四位只可能是a1, a3, a4, a2或者a3, a1, a2, a4。否则,可以反证m1不可能等于m2。所以,m=m1。

2. m1 < m2

(1)n为奇数。m1左侧的元素与m2右侧的元素都不可能是中位数。所谓中位数,就是靠近数组中间最近的元素。因为m1<m2,那么这些元素靠近中心最近的结构就是(m1左侧元素), m1, m2, (m2右侧元素)。即使在这种情况下,括号中的元素也绝不可能成为中位数。而且,当m1与m2距离越来越远时,括号中的元素也与中心越来越远。分析下来,这种情况下,中位数只可能出现在子数组A1[m1, ..]与A2[.., m2]中。可以用递归解决。注意,需要包含m1与m2,因为它们可能是中位数。还需要注意的是,这一定是non-trivial division,即划分后的数组一定比原数组小。因为一定会至少抛弃一个元素,考虑长度为3的数组。所以,递归的base class不需要考虑n=3的情况,因为3一定会划分成长度为1和2的子数组(并且丢弃1,选择2)。那么base class需要考虑长度为2的数组吗?见下。

(2)n为偶数。类似(1)的分析,A1[.., m1)与A2(m2, ..]中的元素都不可能是中位数。而且a1, a2, a3, a4中的任何数都可能是中位数,比如A中的顺序可能为:……a1, a2, a3, a4……,……a1, a3, a2, a4……,……a3, a1, a2, a4……,……a1, a3, a4, a2……等。所以,中位数只可能出现在子数组A1[m1, ..]与A2[.., m2]中。如果n=4,则会分成长度分别为1、3的数组,然后在两个长度为3的数组上递归。如果n=2,因为中位数包含了全部的两个元素,如果按照上面的规则,会继续在长度为2的两个数组上递归下去,无限循环!这就是trivial division,因为可能出现一种情况导致递归时并没有把问题规模减小(哪怕是一个元素)。所以,当n=2时,不能继续递归下去,必须把它作为base case来处理。长度为2的两个有序数组[a1, a2], [a3, a4]的中位数是:(max(a1, a3) + min(a2, a4)) / 2。

3. m1 > m2

类似2的分析,只是把m1与m2交换一下。

代码如下:

#include <iostream>
#include <vector>
#include <algorithm>
#include <cassert>

using namespace std;

class Solution {
public:
   double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
   {
      return getMedian(nums1, 0, nums1.size() - 1, nums2, 0, nums2.size() - 1);
   }

private:
   double getMedian(vector<int>& nums1, int s1, int t1, vector<int>& nums2, int s2, int t2)
   {
      // assure equal size
      assert(t1 - s1 == t2 - s2);

      int n = t1 - s1 + 1;

      // error handling
      if (n <= 0)
         return -1;

      // check original input array size
      if (n == 1)
         return (nums1[s1] + nums2[s2]) / 2;

      // base case
      if (n == 2)
      {
         return (max(nums1[s1], nums2[s2]) + min(nums1[t1], nums2[t2])) / 2;
      }

      double m1 = median(nums1, s1, t1);
      double m2 = median(nums2, s2, t2);

      if (m1 == m2)
         return m1;

      // recursion
      if (m1 < m2)
      {
         if (n % 2 == 0)
            return getMedian(nums1, s1 + n/2-1, t1, nums2, s2, s2 + n/2);
         else
            return getMedian(nums1, s1 + n/2, t1, nums2, s2, s2 + n/2);
      }
      else
      {
         if (n % 2 == 0)
            return getMedian(nums1, s1, s1 + n/2, nums2, s2 + n/2-1, t2);
         else
            return getMedian(nums1, s1, s1 + n/2, nums2, s2 + n/2, t2);
      }
   }

   // return median of a single non-empty sorted array
   double median(vector<int>& nums, int s, int t)
   {
      int n = t - s + 1;
      if (n % 2 == 0)
      {
         return (nums[s + n/2 - 1] + nums[s + n/2]) / 2;
      }
      else
      {
         return nums[s + n/2];
      }
   }
};

在递归函数getMedian()中,虽然根据前面的分析,不会出现n=1的base case(不会通过递归访问到长度为1的数组),但加上n=1的原因是考虑到原始输入数组的大小。还有种方法是放在包装函数findMedianSortedArrays()中判断,好处是不用每次递归都判断一次。

求不同长度两个有序数组的中位数

基本思路还是一样,区别是数组长度不一样,所以可能最后一个数组很短,另一个数组很长,需要更新几种情况。

1. 短数组长度为0。返回长数组的中位数。

2. 短数组长度为1,A1  = {a1}

(1)长数组长度也为1

(2)长数组长度为2,A2 = {a2, a3}。m = median(a1, a2, a3)

(3)长数组长度为大于2的奇数,A2 = {……, a2, a3, a4, ……}(a3是中位数)

o 如果a1落在[a1, a2]或[a2, a3]之间,那么m = (a1 + a3) / 2

o 如果a1落在[……, a2)之间,m = (a2 + a3) / 2

o 如果a1落在(a4, ……]之间,m = (a3 + a4) / 2

a3在每种情况下都出现,区别是其他三个元素。不难得出:m = (a3 + median(a1, a2, a4)) / 2

(4)长数组长度为大于2的偶数,A2 = {……, a2, a3, a4, a5, ……}((a3, a4)/2是中位数)

  类似(3)可得,m = median(a1, a3, a4)

回顾下(2),发现其实是(4)的特殊情况,所以可以和(4)合并。

3. 短数组长度为2,A1 = {a1, a2}

(1)长数组长度也为2,A2 = {a3, a4}。m = median(max(a1, a3), min(a2, a4))

(2)长数组长度为大于2的奇数,A2 = {……, a3, a4, a5, ……}(a4是中位数)

一共7种情况,可以规约成m = median(max(a1, a3), a4, min(a2, a5))。如果得不出这公式,写出7种情况也行。

(3)长数组长度为大于2的偶数,A2 = {……, a3, a4, a5, a6, ……}(a4, a5)/2是中位数

一共15种情况,根据(2)的公式推测m = median(max(a1, a3), a4, a5, min(a2, a6)),可以简单证明该公式的正确性。如果得不出该公式也没关系,大不了求一下长度为6的数组的中位数,也是O(1)。

这里我们假设知道哪个是短数组,所以一个小技巧就是在外层的包装函数中先检测好数组长度。在调用递归函数时,保证传入的参数顺序,一定先是短数组。这样,递归函数就可以避免不必要的if-else。下面是代码:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值