题目
来源:LeetCode.
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。
请你找出并返回这两个正序数组的 中位数 。
看到这个问题我首先想到的方法就是将两个有序数组合并,然后找中位数。
接下来看一下解题思路:
思路一(归并求解):
首先将两个有序数组按序合并存放在一个数组里
然后查找中位数,若有奇数个数据,直接返回中间的数据,
若有偶数个数据,返回中间两个数的平均数
代码示例:
public double findMedianSortedArrays1(int[] nums1, int[] nums2) {
int count1 = nums1.length;
int count2 = nums2.length;
int n = count1 + count2;
//需要申请m + n空间
int[] merge = new int[n];
int i = 0;
int j = 0;
int k = 0;
//有序合并的过程
while(i < count1 && j < count2) {
if(nums1[i] > nums2[j]) {
merge[k++] = nums2[j++];
} else {
merge[k++] = nums1[i++];
}
}
while(i < count1) {
merge[k++] = nums1[i++];
}
while(j < count2) {
merge[k++] = nums2[j++];
}
double result = 0.0;
if((n & 1) == 0){
//有偶数个元素,返回中间两个元素的平均数
result = (merge[n / 2] + merge[(n / 2) - 1]) / 2.0;
} else {
//有奇数个元素,返回中间元素
result = merge[n / 2];
}
return result;
}
总结
这样做时间复杂度 O(m + n),m为nums1的长度;n为nums2的长度;
因为申请了合并后数组的存放空间,所以空间复杂度 O(m + n);
其实不需要申请空间,因为知道数组元素个数 ,所以可以直接定位到中位数下标,这样就不需要申请额外的空间了。
思路二
这里看到了一位大佬的解法:https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-w-2/.
解题思路:
用
l
e
n
len
len 表示合并后数组的长度,如果数组的元素个数是奇数个,只需要知道第
l
e
n
+
1
2
\frac{len + 1}{2}
2len+1 个数就可以了,如果遍历的话需要遍历
l
e
n
2
\frac{len}{2}
2len + 1 次。
如果是偶数,我们需要知道第
l
e
n
2
\frac{len}{2}
2len 和
l
e
n
2
\frac{len}{2}
2len + 1 个数,也是需要遍历
l
e
n
2
\frac{len}{2}
2len + 1 次。所以遍历的话,奇数和偶数都是
l
e
n
2
\frac{len}{2}
2len + 1 次。
返回中位数的话,奇数需要最后一次遍历的结果就可以了,偶数需要最后一次和上一次遍历的结果。
所以这里用两个变量 left 和 right,right 保存当前循环的结果,在每次循环前将 right 的值赋给 left。这样在最后一次循环的时候,left 将得到 right 的值,也就是上一次循环的结果,接下来 right 更新为最后一次的结果。
循环中用 nums1Start 和 nums2Start 分别表示当前指向 nums1 数组和 nums2 数组的位置。如果 nums1Start 还没有到最后并且此时 nums1 位置的数字小于 nums2 位置的数组,那么就可以后移了。也就是 nums1Start<m && nums1[nums1Start]< nums2[nums2Start]。
但如果 nums2 数组此刻已经没有数字了,继续取数字 nums2[ nums2Start ],则会越界,所以判断下 nums2Start 是否大于数组长度了,这样 || 后边的就不会执行了,也就不会导致错误了,所以增加为 nums1Start<m && (nums2Start) >= n || nums1[nums1Start] < nums2[nums2Start]) 。
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m = nums1.length;
int n = nums2.length;
int len = m + n;
int left = 0;
int right = 0;
int nums1Start = 0;
int nums2Start = 0;
//无论是奇数还是偶数,只需要遍历 len / 2 + 1 次
for(int i = 0; i <= len / 2; ++i) {
//这里用于保存上一个值
left = right;
//这里利用 || 的短路运算,防止nums2下标越界
if(nums1Start < m && (nums2Start >= n
|| (nums1[nums1Start] < nums2[nums2Start]))) {
right = nums1[nums1Start++];
} else {
right = nums2[nums2Start++];
}
}
if((len & 1) == 0){
return (left + right) / 2.0;
} else {
return right;
}
}
总结
时间复杂度:遍历 l e n 2 \frac{len}{2} 2len + 1 次,len=m+n,所以时间复杂度依旧是 O(m+n)。
空间复杂度:我们申请了常数个变量,所以空间复杂度是 O(1)。
思路三(二分法:找第K小的数)
大佬的解法:https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-w-2/.
对于寻找第K小的数的代码实现很巧妙。
解题思路:
假设数组
A
A
A 的长度为
m
m
m ,数组
B
B
B的长度为
n
n
n。
根据中位数的定义:
当
m
+
n
m+n
m+n 是奇数时,中位数是两个有序数组中的第
m
+
n
2
\frac{m + n}{2}
2m+n 个元素;
当
m
+
n
m+n
m+n 是偶数时,中位数是两个有序数组中的第
m
+
n
2
\frac{m + n}{2}
2m+n 个元素和第
m
+
n
2
+
1
\frac{m + n}{2}+1
2m+n+1 个元素的平均值。
因此,这道题可以转化成寻找两个有序数组中的第
k
k
k 小的数,其中
k
k
k 为
m
+
n
2
\frac{m + n}{2}
2m+n 或
m
+
n
2
+
1
\frac{m + n}{2}+1
2m+n+1。
要想找到第
k
k
k 个元素,我们可以比较
A
[
k
/
2
−
1
]
\text{A}[k/2-1]
A[k/2−1] 和
B
[
k
/
2
−
1
]
\text{B}[k/2-1]
B[k/2−1]。
由于
A
[
k
/
2
−
1
]
\text{A}[k/2-1]
A[k/2−1] 和
B
[
k
/
2
−
1
]
\text{B}[k/2-1]
B[k/2−1] 的前面分别有
k
2
−
1
\frac{k}{2} - 1
2k−1 个元素,对于
A
[
k
/
2
−
1
]
\text{A}[k/2-1]
A[k/2−1] 和
B
[
k
/
2
−
1
]
\text{B}[k/2-1]
B[k/2−1] 中的较小值,最多只会有
(
k
/
2
−
1
)
+
(
k
/
2
−
1
)
≤
k
−
2
(k/2-1)+(k/2-1) \leq k-2
(k/2−1)+(k/2−1)≤k−2 个元素比它小,那么它就不能是第
k
k
k 小的数了。
因此可以归纳出三种情况:
- 如果 A [ k / 2 − 1 ] \text{A}[k/2-1] A[k/2−1] < B [ k / 2 − 1 ] \text{B}[k/2-1] B[k/2−1],则比 A [ k / 2 − 1 ] \text{A}[k/2-1] A[k/2−1] 小的数最多只有 A \text{A} A 的前 k / 2 − 1 k/2-1 k/2−1 个数和 B \text{B} B 的前 k / 2 − 1 k/2-1 k/2−1 个数,即比 A [ k / 2 − 1 ] \text{A}[k/2-1] A[k/2−1] 小的数最多只有 k / 2 − 1 k/2-1 k/2−1 + k / 2 − 1 k/2-1 k/2−1 = k − 2 k−2 k−2 个,因此 A [ k / 2 − 1 ] \text{A}[k/2-1] A[k/2−1] 不可能是第 k k k个数, A [ 0 ] \text{A}[0] A[0] 到 A [ k / 2 − 1 ] \text{A}[k/2-1] A[k/2−1] 也都不可能是第 k k k 个数,可以全部排除。
- 如果 A [ k / 2 − 1 ] \text{A}[k/2-1] A[k/2−1] > B [ k / 2 − 1 ] \text{B}[k/2-1] B[k/2−1],则可以排除 B [ 0 ] \text{B}[0] B[0] 到 B [ k / 2 − 1 ] \text{B}[k/2-1] B[k/2−1]。
- 如果 A [ k / 2 − 1 ] \text{A}[k/2-1] A[k/2−1] = B [ k / 2 − 1 ] \text{B}[k/2-1] B[k/2−1],则可以归入第一种情况处理。
下面举个例子来描述上述算法:
假设给定如上两个数组,且数组长度分别为 3 和 6,长度之和是 9,中位数是两个有序数组中的第
m
+
n
2
+
1
\frac{m + n}{2} + 1
2m+n+1 = 5 个元素,因此需要找到第 k=5 个元素。
比较两个有序数组中下标为 k / 2 - 1 = 1 的数,即
A
[
1
]
\text{A}[1]
A[1] 和
B
[
1
]
\text{B}[1]
B[1],如下面所示:
由于 A [ 1 ] < B [ 1 ] \text{A}[1] < \text{B}[1] A[1]<B[1],因此排除 A [ 0 ] \text{A}[0] A[0] 到 A [ 1 ] \text{A}[1] A[1],即数组 A \text{A} A 的下标偏移(offset)变为 2,同时更新 k k k 的值: k = k − k / 2 = 3 k = k - k / 2 = 3 k=k−k/2=3。
下一步寻找,比较两个有序数组中下标为
k
/
2
−
1
=
0
k / 2 - 1 = 0
k/2−1=0 的数,即
A
[
2
]
\text{A}[2]
A[2] 和 $\text{B}[0],如下面所示,其中蓝色部分表示已经被排除的数。
由于
B
[
0
]
<
A
[
2
]
\text{B}[0] < \text{A}[2]
B[0]<A[2],因此排除
B
[
0
]
\text{B}[0]
B[0],即数组
B
\text{B}
B 的下标偏移变为 1,同时更新
k
k
k 的值:
k
=
k
−
k
/
2
=
2
k = k - k / 2 = 2
k=k−k/2=2。
下一步寻找,比较两个有序数组中下标为
k
/
2
−
1
=
0
k / 2 - 1 = 0
k/2−1=0 的数,即比较
A
[
2
]
\text{A}[2]
A[2] 和
B
[
1
]
\text{B}[1]
B[1],如下面所示,其中蓝色部分表示已经被排除的数。
由于
B
[
1
]
<
A
[
2
]
\text{B}[1] < \text{A}[2]
B[1]<A[2],因此排除
B
[
1
]
\text{B}[1]
B[1],即数组
B
\text{B}
B 的下标偏移变为 2,同时更新
k
k
k 的值:
k
=
k
−
k
/
2
=
1
k = k - k / 2 = 1
k=k−k/2=1。
由于
k
k
k 的值变成 1,因此比较两个有序数组中的未排除下标范围内的第一个数,如下面所示,其中蓝色部分表示已经被排除的数。
其中较小的数即为第 k 个数,因此第 k k k 个数是 B [ 2 ] = A [ 2 ] = 4 \text{B}[2] = A[2] = 4 B[2]=A[2]=4。
过程中每次选取的下标值为
k
/
2
−
1
k / 2 - 1
k/2−1 的数,可能会出现下面这种情况:其中蓝色部分表示已经被排除的数。
可能会遇到数组长度小于
k
/
2
k / 2
k/2 的时候。此时将箭头指向它的末尾就可以了。而且在算法进行中,
k
k
k 的值都有可能从奇数变为偶数,最终都会变为 1 或者由于一个数组空了,直接返回结果。
采用递归的思路,为了防止数组长度小于 k/2,所以每次比较 min(k/2,len(数组) )对应的数字,把小的那个对应的数组的数字排除,将两个新数组进入递归,并且 k 要减去排除的数字的个数。递归出口就是当 k=1 或者其中一个数组长度是 0 了。
public double findMedianSortedArrays4(int[] nums1, int[] nums2) {
int m = nums1.length;
int n = nums2.length;
//数组中第K个数,也就是(m+n)/2 或 (m+n)/2 + 1
int left = (m + n + 1) / 2;
int right = (m + n + 2) / 2;;
//将偶数和奇数的情况合并,如果是奇数,会求两次同样的 k 。
return (findMinK(nums1, 0, m - 1, nums2, 0, n - 1, left) + findMinK(nums1, 0, m - 1, nums2, 0, n - 1, right)) / 2.0;
}
private int findMinK(int[] nums1, int start1, int end1, int[] nums2, int start2, int end2, int k) {
int len1 = end1 - start1 + 1;
int len2 = end2 - start2 + 1;
//让 len1 的长度小于 len2,这样就能保证如果有数组空了,一定是 len1
if(len1 > len2) {
return findMinK(nums2, start2, end2, nums1, start1, end1, k);
}
//如果某一数组为空,直接返回另一数组的中位数
if(len1 == 0) {
return nums2[start2 + k - 1];
}
//k = 1,代表当前未排除数组的第一个小的元素,所以返回相对小的就是中位数
if(k == 1) {
return Math.min(nums1[start1], nums2[start2]);
}
//为了防止k / 2 大于数组长度,也就是数组下标越界,所以选取较小的,
// 也就是说如果数组的长度比 k / 2小,那么直接定位到数组末尾就可
int i = start1 + Math.min(len1, k / 2) - 1;
int j = start2 + Math.min(len2, k / 2) - 1;
if(nums1[i] > nums2[j]) {
//j + 1,从下一个位置访问,也就是排除nums2[j]之前的元素
//k - (j - start2 + 1)参数,下一个第K小的元素,也就是 k = k - k / 2;
return findMinK(nums1, start1, end1, nums2, j + 1, end2, k - (j - start2 + 1));
} else {
return findMinK(nums1, i + 1, end1, nums2, start2, end2, k - (i - start1 + 1));
}
}
}
总结
时间复杂度:每一轮循环可以将查找范围减少一半,所以是O(log(m+n)。
空间复杂度:O(1)。
思路四(二分法:分割数组)
这个是官方的解题思路,看了这个思路我才直到这道题为什么是hard了。下面来看一下官方的解题思路。
大致思路是: 先将nums1和nums2两个数组分割,满足nums1[i - 1] < nums2[j] && nums2[j - 1] < nums1[i];条件;
并且如果两个数组的元素个数是偶数个,则分割后使左右两边的元素个数相等;如果两个数组的元素个数是奇数个,则分割后使左元素比右边个数多1;
这个算法的核心是如何分割,从而满足nums1[i - 1] < nums2[j] && nums2[j - 1] < nums1[i];条件。
思路:
1.这里需要理解中位数的作用是什么?。在统计中,中位数被用来:
将一个集合划分为两个长度相等的子集,其中一个子集中的元素总是大于另一个子集中的元素。
如果理解了中位数的划分作用,就很接近答案了。
首先,在任意位置 i ,j将 nums1和nums2数组划分成两个部分:
由于 nums1 中有 m 个元素, 所以有m+1 种划分的方法( i∈[0,m])。
len(leftNums1) = i, len(rightNums1) = m − i.
注意:当i = 0 时,leftNums1为空集, 而当 i = m 时, rightNums1为空集。
采用同样的方式,在任意位置 j 将 nums2 划分成两个部分.
将 leftNums1和leftNums2放入一个集合,并将rightNums1 和 rightNums2 放入另一个集合。 再把这两个新的集合分别命名为 leftPart 和 rightPart
当nums1和nums2 的总长度是偶数时,如果可以确认:
len(leftPart) = len(rightPart);
max(leftPart) <= min(rightPart);
那么,{nums1, nums2} 中的所有元素已经被划分为相同长度的两个部分,且前一部分中的元素总是小于或等于后一部分中的元素。中位数就是前一部分的最大值和后一部分的最小值的平均值:
m
e
d
i
a
n
median
median=
(
m
a
x
(
l
e
f
t
P
a
r
t
)
+
m
i
n
(
r
i
g
h
t
P
a
r
t
)
)
2.0
\frac{(max(leftPart) + min(rightPart))}{2.0}
2.0(max(leftPart)+min(rightPart));
当 nums1和 nums2 的总长度是奇数时,如果可以确认:
len(leftPart) = len(rightPart) + 1;
max(leftPart) <= min(rightPart);
那么,{nums1, nums2}中的所有元素已经被划分为两个部分,前一部分比后一部分多一个元素,且前一部分中的元素总是小于或等于后一部分中的元素。中位数就是前一部分的最大值:
m
e
d
i
a
n
median
median=
m
a
x
(
l
e
f
t
P
a
r
t
)
max(leftPart)
max(leftPart)
第一个条件对于总长度是偶数和奇数的情况有所不同,但是可以将两种情况合并。第二个条件对于总长度是偶数和奇数的情况是一样的。
要确保这两个条件,只需要保证:
-
i + j = m − i + n − j i + j = m - i + n - j i+j=m−i+n−j(当 m + n m+n m+n 为偶数)或 i + j = m − i + n − j + 1 i + j = m - i + n - j + 1 i+j=m−i+n−j+1(当 m + n m+n m+n 为奇数)。等号左侧为前一部分的元素个数,等号右侧为后一部分的元素个数。将 i i i 和 j j j 全部移到等号左侧,我们就可以得到 i + j = m + n + 1 2 i+j = \frac{m + n + 1}{2} i+j=2m+n+1。这里的分数结果只保留整数部分。
-
0 ≤ i ≤ m 0 \leq i \leq m 0≤i≤m, 0 ≤ j ≤ n 0 \leq j \leq n 0≤j≤n。如果我们规定 nums1 的长度小于等于 nums2 的长度,即 m ≤ n m \leq n m≤n。这样对于任意的 i ∈ [ 0 , m ] i \in [0, m] i∈[0,m],都有 j = m + n + 1 2 − i ∈ [ 0 , n ] j = \frac{m + n + 1}{2} - i \in [0, n] j=2m+n+1−i∈[0,n],那么我们在 [0, m]的范围内枚举 i 并得到 j,就不需要额外的性质了。
- 如果 nums1 的长度较大,那么我们只要交换 nums1 和 nums2 即可。
- 如果 m > n m > n m>n ,那么得出的 j 有可能是负数。
- nums2 [ j − 1 ] ≤ nums1 [ i ] \text{nums2}[j-1] \leq \text{nums1}[i] nums2[j−1]≤nums1[i] 以及 nums1 [ i − 1 ] ≤ nums2 [ j ] \text{nums1}[i-1] \leq \text{nums2}[j] nums1[i−1]≤nums2[j],即前一部分的最大值小于等于后一部分的最小值。
为了简化分析,假设
nums1
[
i
−
1
]
\text{nums1}[i-1]
nums1[i−1],
nums2
[
j
−
1
]
\text{nums2}[j-1]
nums2[j−1],
nums1
[
i
]
\text{nums1}[i]
nums1[i],
nums2
[
j
]
\text{nums2}[j]
nums2[j] 总是存在。对于
i
=
0
i=0
i=0、
i
=
m
i=m
i=m、
j
=
0
j=0
j=0、
j
=
n
j=n
j=n 这样的临界条件,我们只需要规定
nums1
[
−
1
]
=
nums2
[
−
1
]
=
−
∞
\text{nums1}[-1]=\text{nums2}[-1]=-\infty
nums1[−1]=nums2[−1]=−∞,
A
[
m
]
=
B
[
n
]
=
∞
\text{A}[m]=\text{B}[n]=\infty
A[m]=B[n]=∞即可。
这也是比较直观的:当一个数组不出现在前一部分时,对应的值为负无穷,就不会对前一部分的最大值产生影响;当一个数组不出现在后一部分时,对应的值为正无穷,就不会对后一部分的最小值产生影响。
分割数组可能会出现下面四种情况:
所以需要做的是:
在 [0, m]中找到 i,使得:
nums2 [ j − 1 ] ≤ nums1 [ i ] \text{nums2}[j-1] \leq \text{nums1}[i] nums2[j−1]≤nums1[i] 且 nums1 [ i − 1 ] ≤ nums2 [ j ] \text{nums1}[i-1] \leq \text{nums2}[j] nums1[i−1]≤nums2[j],
其中 j = m + n + 1 2 − i j = \frac{m + n + 1}{2} - i j=2m+n+1−i
证明它等价于:
在 [0, m] 中找到最大的 i,使得:
nums1 [ i − 1 ] ≤ nums2 [ j ] \text{nums1}[i-1] \leq \text{nums2}[j] nums1[i−1]≤nums2[j],其中 j = m + n + 1 2 − i j = \frac{m + n + 1}{2} - i j=2m+n+1−i
这是因为:
- 当
i
i
i 从
0
∼
m
0 \sim m
0∼m 递增时,
nums1
[
i
−
1
]
\text{nums1}[i-1]
nums1[i−1] 递增,
nums2
[
j
]
\text{nums2}[j]
nums2[j]
递减,所以一定存在一个最大的 i i i 满足 nums1 [ i − 1 ] ≤ nums2 [ j ] \text{nums1}[i-1] \leq \text{nums2}[j] nums1[i−1]≤nums2[j]; - 如果 i i i 是最大的,那么说明 i + 1 i+1 i+1 不满足。将 i + 1 i+1 i+1 带入可以得到 nums1 [ i ] > nums2 [ j − 1 ] \text{nums1}[i] > \text{nums2}[j-1] nums1[i]>nums2[j−1],也就是 nums2 [ j − 1 ] < nums1 [ i ] \text{nums2}[j - 1] < \text{nums1}[i] nums2[j−1]<nums1[i],就和进行等价变换前 i i i 的性质一致了。
因此可以对 i i i 在 [0, m] 的区间上进行二分搜索,找到最大的满足 nums1 [ i − 1 ] ≤ nums2 [ j ] \text{nums1}[i-1] \leq \text{nums2}[j] nums1[i−1]≤nums2[j] 的 i i i 值,就得到了划分的方法。此时,划分前一部分元素中的最大值,以及划分后一部分元素中的最小值,才可能作为就是这两个数组的中位数。
来源:力扣(LeetCode).
代码示例:
public double findMedianSortedArrays2(int[] nums1, int[] nums2) {
int m = nums1.length;
int n = nums2.length;
//确保nums1总是最短的,方便后面的计算
if(m > n) {
return findMedianSortedArrays2(nums2, nums1);
}
//分割后两个数组左边的所有元素和
//m + (n - m + 1) / 2 等价于 m + n + 1 / 2;是为了防止n + m溢出
//这也是为什么需要上面保证nums1元素个数最少的原因
int totalLeft = m + (n - m + 1) / 2;
int left = 0;
int right = m;
//采用二分法在[0, m]区间找
//满足nums1[i - 1] < nums2[j] && nums2[j - 1] < nums1[i];
//条件的过程
while(left < right) {
//寻找nums1数组中间下标,
int i = left + (right - left + 1) / 2;
//nums2数组中间下标
int j = totalLeft - i;
//满足这个条件,说明nums1数组找到位置偏后了,
//需要在前面找,否则在后面找
if(nums1[i - 1] > nums2[j]) {
//下一轮寻找区间[left, i - 1]
right = i - 1;
} else {
//下一轮寻找区间[i, right]
left = i;
}
}
//int i = left + (right - left + 1) / 2; 这里分子为什么 + 1,
//有以下几个原因:
//1.不用考虑数组元素个数有奇数/偶数个,因为若是奇数个,
//分割后左边多一个元素,若是偶数个,因为是向下取整,不影响
//2.判断nums1[i - 1] > nums2[j]条件时[i - 1]不会出现下标越界错误
//3.执行left = i;这个时,若数组只剩下两个元素[left, right],
//就会陷入死循环,若是+1的话,就可以跳出while循环了
//语句执行跳出while循环,说明找到了满足条件的分割点
int i = left;
int j = totalLeft - i;
//这里对数组下标越界的情况加以判断,为什么会出现这种情况呢?
//就是我们找到的分割点可能在数组的最左侧或者最右侧
//这个时候给它一个最大值或最小值,
//保证在判断最大值和最小值的时候不被选中
int leftNum1Max = (i == 0 ? Integer.MIN_VALUE : nums1[i - 1]);
int rightNum1Min = (i == m ? Integer.MAX_VALUE : nums1[i]);
int leftNum2Max = (j == 0 ? Integer.MIN_VALUE : nums2[j - 1]);
int rightNum2Min = (j == n ? Integer.MAX_VALUE : nums2[j]);
if(((m + n) & 1) == 0){
//若数组有偶数个元素,返回左边最大值和右边最小值的平均数
return ((Math.max(leftNum1Max, leftNum2Max) +
Math.min(rightNum1Min, rightNum2Min)) / 2.0);
} else {
//若数组有奇数个元素,返回左边最大值即可
return (double)Math.max(leftNum1Max, leftNum2Max);
}
}
总结
通过
i
i
i 和
j
j
j 的关系
j
=
m
+
n
+
1
2
−
i
j = \frac{m + n + 1}{2} - i
j=2m+n+1−i,所以只需要在相对较短的数组nums1的区间[0, m]找就可以了。
时间复杂度:
O
(
log
min
(
m
,
n
)
)
)
O(\log\min(m,n)))
O(logmin(m,n))),其中
m
m
m 和
n
n
n 分别是数组
nums1
\textit{nums1}
nums1 和
nums2
\textit{nums2}
nums2的长度。查找的区间是 [0, m],而该区间的长度在每次循环之后都会减少为原来的一半。所以,只需要执行
log
m
\log m
logm 次循环。
由于我们可能需要交换
nums1
\textit{nums1}
nums1和
nums2
\textit{nums2}
nums2 使得
m
≤
n
m \leq n
m≤n,因此时间复杂度是
O
(
log
min
(
m
,
n
)
)
)
O(\log\min(m,n)))
O(logmin(m,n)))。
空间复杂度:O(1)。
上面代码的while循环里是基于 nums1 [ i − 1 ] ≤ nums2 [ j ] \text{nums1}[i-1] \leq \text{nums2}[j] nums1[i−1]≤nums2[j]表达式分割数组的,其实也可以根据 nums2 [ j − 1 ] ≤ nums1 [ i ] \text{nums2}[j-1] \leq \text{nums1}[i] nums2[j−1]≤nums1[i] 分割数组。
while(left < right) {
//寻找nums1数组中间下标,
int i = left + (right - left) / 2;
//nums2数组中间下标
int j = totalLeft - i;
//满足这个条件,说明nums1数组找到位置偏后了,
//需要在前面找,否则在后面找
//由于取中位数 i 没有 +1向上取整,
//所以取中位数 i 在[0, m]就不会取到 m;
//那么 j 就一定会在左边取到数值,因此nums2[j - 1]
//就不会出现下标越界错误
if(nums2[j - 1] > nums1[i]) {
//下一轮寻找区间[i + 1, right]
//这里 i + 1了,所以 i 在取中位数的时候就不用 +1
//向上取整了
left = i + 1;
} else {
//下一轮寻找区间[left, i]
right = i;
}
}