@LeetCode寻找两个有序数组的中位数--Median of Two Sorted Arrays[C++]

@LeetCode寻找两个有序数组的中位数--Median of Two Sorted Arrays[C++]

问题描述

给定两个大小为 m 和 n 的有序数组 nums1nums2

请找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O ( l o g ( m + n ) ) O(log(m+n)) O(log(m+n))

可以假设 nums1nums2 不会同时为空。

示例1:

nums1 = [1, 3]
nums2 = [2]


则中位数是 2.0

示例2:

nums1 = [1, 2]
nums2 = [3, 4]


则中位数是 (2 + 3) / 2 = 2.5

解题方法及复杂度分析

递归法

在统计中,中位数被用来:

将一个集合划分为两个长度相等的子集,其中一个子集中的元素总是大于另一个子集中的元素。

中位数的作用是用来划分数组,我们依据这个原理来分析。

首先,在 A 中任一位置划分,分成两个部分:
A 数组划分示意图

图1-1 A 数组划分示意图

由于 A 中有 m m m 个元素,所以有 m + 1 m+1 m+1 种划分的方法 ( i = 0 i = 0 i=0 ~ m m m)。

可以知道:

l e n ( l e f t _ A ) = i len(left\_A) = i len(left_A)=i, l e n ( r i g h t _ A ) = m − i len(right\_ A) = m-i len(right_A)=mi
注意:当 i = 0 i=0 i=0 时,left_A为空集,而当 i = m i = m i=m 时,right_A为空集。

采用同样的方式,在 B 中任一位置划分成两个部分:
B 数组划分示意图

图1-2 B 数组划分示意图

将left_A 和 left_B 放入一个集合,并将 right_A 和 right_B 放入另一个集合。再把这两个新的集合分别命名为 left_part 和 right_part:
集合合并示意图

图1-3 A、B 数组合并示意图

如果可以确认:

  1. l e n ( l e f t _ p a r t ) = l e n ( r i g h t _ p a r t ) len(left\_part) = len(right\_part) len(left_part)=len(right_part)
  2. m a x ( l e f t _ p a r t ) ⩽ m a x ( r i g h t _ p a r t ) max(left \_ part) \leqslant max(right\_ part) max(left_part)max(right_part)

那么,我们已经将 {A, B} 中的所有元素划分为相同长度的两个部分,且其中一部分中的元素总是大于另一部分中的元素。那么:
m e d i a n = m a x ( l e f t _ p a r t ) + m i n ( r i g h t _ p a r t 2 median = \frac{max(left\_part)+min(right\_part}{2} median=2max(left_part)+min(right_part
要确保这两个条件,只需要保证:

i + j = m − i + n − j ( 或 : m − i + n − j + 1 ) 如 果 n ⩾ m , 只 需 要 使 i ∈ [ 0 , m ] ,   j = m + n + 1 2 − i i+j=m-i+n-j(或:m-i+n-j+1) 如果n\geqslant m,只需要使i \in [0,m],\ j=\frac{m+n+1}{2}-i i+j=mi+nj:mi+nj+1nm使i[0,m], j=2m+n+1i

ps.1 为了简化分析,假设A[ i − 1 i-1 i1], B[ j − 1 j-1 j1], A[ i i i], B[ j j j]总是存在,哪怕出现 i = 0 , i = m , j = 0 i=0,i=m,j=0 i=0,i=m,j=0,或是 j = n j=n j=n 这样的临界条件。我们在最后讨论临界值。

ps.2 为什么 n ⩾ m n\geqslant m nm?由于 0 ⩽ i ⩽ m 0\leqslant i\leqslant m 0im j = m + n + 1 2 − i j=\frac{m+n+1}{2}-i j=2m+n+1i,必须确保 j j j 不是负值。如果 n &lt; m n &lt; m n<m,那么 j j j 将可能是负数,而这会造成错误的答案。

所以,需要做的是:

在 [0, m m m] 中搜索并找到目标对象 i i i,以使:
B [ j − 1 ] ≤ A [ i ] 且 A [ i − 1 ] ≤ B [ j ] , 其 中 j = m + n + 1 2 − i B[j-1]\le A[i] 且A[i-1]\le B[j],其中j=\frac{m+n+1}{2}-i B[j1]A[i]A[i1]B[j]j=2m+n+1i

接着,按照以下步骤来进行二叉搜索:

  1. 设 imin = 0, imax = m m m,然后开始在[imin, imax]中进行搜索。
  2. i = i m i n + i m a x 2 ,   j = m + n + 1 2 − i i=\frac{imin+imax}{2},\ j=\frac{m+n+1}{2}-i i=2imin+imax, j=2m+n+1i
  3. 现在有 l e n ( l e f t _ p a r t ) = l e n ( r i g h t _ p a r t ) len(left\_ part)=len(right\_ part) len(left_part)=len(right_part)。只会遇到三种情况:
    B [ j − 1 ] ≤ A [ i ]   且   A [ i − 1 ] ≤ B [ j ] B[j-1]\le A[i]\ 且\ A[i-1]\le B[j] B[j1]A[i]  A[i1]B[j]:这意味着找到目标对象 i i i,所以可以停止搜索。
    B [ j − 1 ] &gt; A [ i ] B[j-1] &gt; A[i] B[j1]>A[i]:这意味着 A [ i ] A[i] A[i] 太小,必须调整 i i i 以使 B [ j − 1 ] ≤ A [ i ] B[j-1]\le A[i] B[j1]A[i]。增大 i i i,这是因为当 i i i 被增大, j j j 就会被减小,因此B[ j − 1 j-1 j1]会减小,而A[ i i i]会增加,那么 B [ j − 1 ] ≤ A [ i ] B[j-1]\le A[i] B[j1]A[i] 就可能被满足。若减小 i i i,同理可分析无法满足 B [ j − 1 ] ≤ A [ i ] B[j-1]\le A[i] B[j1]A[i]。所以,必须将搜索范围调整为 [ i + 1 [i+1 [i+1, imax]。因此,设 imin = i + 1 i+1 i+1,并转到步骤2。
    A [ i − 1 ] &gt; B [ j ] A[i-1]&gt;B[j] A[i1]>B[j]:这意味着A[ i − 1 i-1 i1]太大,必须减小 i i i 以使 A [ i − 1 ] ≤ B [ j ] A[i-1]\le B[j] A[i1]B[j]。也就是说,必须将搜索范围调整为 [imin, i − 1 i - 1 i1]。因此,设 imax = i − 1 i-1 i1,并转到步骤2。

当找到目标对象 i i i 时,中位数为:

m a x ( A [ i − 1 ] , B [ j − 1 ] ) , 当 m + n 为 奇 数 时 max(A[i-1],B[j-1]),当m+n为奇数时 max(A[i1],B[j1])m+n


m a x ( A [ i − 1 ] , B [ j − 1 ] ) + m i n ( A [ i ] , B [ j ] ) 2 , 当 m + n 为 偶 数 时 \frac{max(A[i-1],B[j-1])+min(A[i],B[j])}{2},当m+n为偶数时 2max(A[i1],B[j1])+min(A[i],B[j])m+n

现在,考虑临界值 i = 0 ,   i = m ,   j = 0 ,   j = n i=0, \ i=m,\ j=0,\ j=n i=0, i=m, j=0, j=n,此时 A [ i − 1 ] ,   A [ i ] ,   B [ j − 1 ] ,   B [ j ] A[i-1],\ A[i],\ B[j-1],\ B[j] A[i1], A[i], B[j1], B[j]可能不存在。

首先需要确保 m a x ( l e f t _ p a r t ) ≤ m i n ( r i g h t _ p a r t ) max(left\_ part)\le min(right\_ part) max(left_part)min(right_part)。因此,如果 i i i
j j j 不是临界值(这意味着 A [ i − 1 ] , B [ j − 1 ] , A [ i ] , B [ j ] A[i-1],B[j-1],A[i],B[j] A[i1],B[j1],A[i],B[j] 全部存在),那么必须同时检查 B [ j − 1 ] ≤ A [ i ] B[j-1]\le A[i] B[j1]A[i] 以及 A [ i − 1 ] ≤ B [ j ] A[i-1]\le B[j] A[i1]B[j] 是否成立。但是如果 A [ i − 1 ] ,   B [ j − 1 ] ,   A [ i ] ,   B [ j ] A[i-1],\ B[j-1],\ A[i],\ B[j] A[i1], B[j1], A[i], B[j] 中部分不存在,那么只需要检查这两个条件中的一个(或不需要检查)。所以,需要做的是:

在[0, m m m]中搜索并找到目标对象 i i i,以使:
( j = 0   o r   i = m   o r   B [ j − 1 ] ≤ A [ i ] )   或 是   ( i = 0   o r   j = n   o r   A [ i − 1 ] ≤ B [ j ] ) , 其 中 j = m + n + 1 2 − i (j=0\ or\ i=m\ or\ B[j-1]\le A[i])\ 或是\ (i=0\ or\ j=n\ or\ A[i-1]\le B[j]),其中j=\frac{m+n+1}{2}-i (j=0 or i=m or B[j1]A[i])  (i=0 or j=n or A[i1]B[j])j=2m+n+1i

在循环搜索中,只会遇到三种情况:

  1. ( j = 0   o r   i = m   o r   B [ j − 1 ] ≤ A [ i ] )   或 是   ( i = 0   o r   j = n   o r A [ i − 1 ] ≤ B [ j ] ) (j=0\ or\ i=m\ or\ B[j-1]\le A[i])\ 或是\ (i=0\ or\ j = n\ or A[i-1]\le B[j]) (j=0 or i=m or B[j1]A[i])  (i=0 or j=n orA[i1]B[j])
  2. i &lt; m   a n d   B [ j − 1 ] &gt; A [ i ] i &lt; m\ and\ B[j-1]&gt;A[i] i<m and B[j1]>A[i],这意味着 i i i 太小,必须增大它。
  3. i &gt; 0   a n d   A [ i − 1 ] &gt; B [ j ] i&gt;0\ and\ A[i-1]&gt;B[j] i>0 and A[i1]>B[j],这意味着 i i i 太大,必须减小它。

ps. 为什么在遇到三种情况时不讨论 j j j 的情况呢?

m ≤ n ,   i &lt; m ⟶ j = m + n + 1 2 − i &gt; m + n + 1 2 − m ≥ 2 m + 1 2 − m ≥ 0 m\le n,\ i&lt;m\longrightarrow j=\frac{m+n+1}{2}-i&gt;\frac{m+n+1}{2}-m\ge \frac{2m+1}{2}-m\ge 0 mn, i<mj=2m+n+1i>2m+n+1m22m+1m0
m ≤ n ,   i &gt; 0 ⟶ j = m + n + 1 2 − i &lt; m + n + 1 2 ≤ 2 n + 1 2 ≤ n m\le n,\ i&gt;0\longrightarrow j=\frac{m+n+1}{2}-i&lt;\frac{m+n+1}{2}\le \frac{2n+1}{2}\le n mn, i>0j=2m+n+1i<2m+n+122n+1n

所以, i &lt; m ⟶ j &gt; 0 i&lt;m\longrightarrow j&gt;0 i<mj>0 以及 i &gt; 0 ⟶ j &lt; n i&gt;0\longrightarrow j&lt;n i>0j<n 始终成立。

复杂度分析

  • 时间复杂度: O ( l o g ( m + n ) ) O(log(m+n)) O(log(m+n))
    – 因为算法需要二叉搜索A[ i i i]和B[ j j j],而 i ∈ [ 0 , m ] ,   j = m + n + 1 2 − i i\in [0,m],\ j=\frac{m+n+1}{2}-i i[0,m], j=2m+n+1i,所以时间复杂度为 O ( l o g ( m + n ) ) O(log(m+n)) O(log(m+n))

程序实现

	class Solution {
	public:
		double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
		{
			int m = nums1.size();
			int n = nums2.size();
			if(m > n) {
				vector<int> temp = nums1; nums1 = nums2; nums2 = temp;
				int tmp = m; m = n; n = tmp;
			}
			int iMin = 0, iMax = m, halfLen = (m + n + 1) / 2;
			while(iMin <= iMax) {
				int i = (iMin + iMax) / 2;
				int j = halfLen - i;
				if(i < iMax && nums2[j - 1] > nums1[i] {
					iMin = i + 1;
				}
				else if(i > iMin && nums1[i - 1] > nums2[j]) {
					iMax = i - 1;
				}
				else {
					int maxLeft = 0;
					if(!i) { maxLeft = nums2[j - 1]; }
					else if(!j) { maxLeft = nums1[i - 1]; }
					else { maxLeft = max(nums1[i - 1], nums2[j - 1]); }
					if((m + n) % 2) { return maxLeft; }
					int minRight = 0;
					if(i == m) { minRight = nums2[j]; }
					else if(j == n) { minRight = nums1[i]; }
					else {minRight = min(nums2[j], nums1[i]); }
					return (maxLeft + minRight) / 2.0;
				}
			}
			return 0.0;
		}
	};

@ 山东·威海 2019.01.28

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值