二分查找题目:寻找两个正序数组的中位数
文章目录
- 题目
- 标题和出处
- 难度
- 题目描述
- 要求
- 示例
- 数据范围
- 解法一
- 思路和算法
- 代码
- 复杂度分析
- 解法二
- 思路和算法
- 代码
- 复杂度分析
题目
标题和出处
标题:寻找两个正序数组的中位数
出处:4. 寻找两个正序数组的中位数
难度
8 级
题目描述
要求
给定两个大小分别为 m \texttt{m} m 和 n \texttt{n} n 的升序数组 nums1 \texttt{nums1} nums1 和 nums2 \texttt{nums2} nums2,返回这两个升序数组的中位数。
要求时间复杂度是 O(log (m + n)) \texttt{O(log (m + n))} O(log (m + n))。
示例
示例 1:
输入: nums1 = [1,3], nums2 = [2] \texttt{nums1 = [1,3], nums2 = [2]} nums1 = [1,3], nums2 = [2]
输出: 2.00000 \texttt{2.00000} 2.00000
解释:合并数组是 [1,2,3] \texttt{[1,2,3]} [1,2,3],中位数是 2 \texttt{2} 2。
示例 2:
输入: nums1 = [1,2], nums2 = [3,4] \texttt{nums1 = [1,2], nums2 = [3,4]} nums1 = [1,2], nums2 = [3,4]
输出: 2.50000 \texttt{2.50000} 2.50000
解释:合并数组是 [1,2,3,4] \texttt{[1,2,3,4]} [1,2,3,4],中位数是 2 + 3 2 = 2.5 \dfrac{\texttt{2} + \texttt{3}}{\texttt{2}} = \texttt{2.5} 22+3=2.5。
数据范围
- nums1.length = m \texttt{nums1.length} = \texttt{m} nums1.length=m
- nums2.length = n \texttt{nums2.length} = \texttt{n} nums2.length=n
- 0 ≤ m ≤ 1000 \texttt{0} \le \texttt{m} \le \texttt{1000} 0≤m≤1000
- 0 ≤ n ≤ 1000 \texttt{0} \le \texttt{n} \le \texttt{1000} 0≤n≤1000
- 1 ≤ m + n ≤ 2000 \texttt{1} \le \texttt{m} + \texttt{n} \le \texttt{2000} 1≤m+n≤2000
- -10 6 ≤ nums1[i], nums2[i] ≤ 10 6 \texttt{-10}^\texttt{6} \le \texttt{nums1[i], nums2[i]} \le \texttt{10}^\texttt{6} -106≤nums1[i], nums2[i]≤106
解法一
思路和算法
已知两个升序数组的长度分别是 m m m 和 n n n。计算两个升序数组的中位数可以转换成找到两个升序数组的所有元素中的第 k k k 小元素,其中 0 ≤ k < m + n 0 \le k < m + n 0≤k<m+n。用 total = m + n \textit{total} = m + n total=m+n 表示两个升序数组的长度之和。当 total \textit{total} total 是奇数时, k = total − 1 2 k = \dfrac{\textit{total} - 1}{2} k=2total−1,第 k k k 小元素即为中位数;当 total \textit{total} total 是偶数时,分别取 k = total 2 − 1 k = \dfrac{\textit{total}}{2} - 1 k=2total−1 和 k = total 2 k = \dfrac{\textit{total}}{2} k=2total,两次第 k k k 小元素的平均数即为中位数。因此,根据两个升序数组的长度之和是奇数或偶数,执行一次或两次寻找第 k k k 小元素的操作,即可得到中位数。
由于题目要求时间复杂度是 O ( log ( m + n ) ) O(\log (m + n)) O(log(m+n)),因此要求每次寻找第 k k k 小元素的操作的时间复杂度是 O ( log ( m + n ) ) O(\log (m + n)) O(log(m+n))。需要使用二分查找实现。
用 k k k 表示目标值在剩余元素中的序号( k k k 从 0 0 0 开始,序号为 k k k 表示剩余元素中有 k k k 个元素小于等于目标值),用 index 1 \textit{index}_1 index1 和 index 2 \textit{index}_2 index2 分别表示数组 nums 1 \textit{nums}_1 nums1 和 nums 2 \textit{nums}_2 nums2 的首个剩余元素的下标,初始时 index 1 \textit{index}_1 index1 和 index 2 \textit{index}_2 index2 都等于 0 0 0。剩余元素表示可能是目标值的元素,查找过程中将不可能是目标值的元素排除。
每次查找时,分别考虑两个数组的剩余元素中最小的 ⌈ k 2 ⌉ \Big\lceil \dfrac{k}{2} \Big\rceil ⌈2k⌉ 个元素,共考虑 k + 1 k + 1 k+1 个元素(当 k k k 是奇数时)或 k k k 个元素(当 k k k 是偶数时),这些元素在两个数组中的下标范围分别是 nums 1 \textit{nums}_1 nums1 的下标范围 [ index 1 , endIndex 1 ] [\textit{index}_1, \textit{endIndex}_1] [index1,endIndex1] 和 nums 2 \textit{nums}_2 nums2 的下标范围 [ index 2 , endIndex 2 ] [\textit{index}_2, \textit{endIndex}_2] [index2,endIndex2],其中 endIndex 1 = index 1 + ⌊ k − 1 2 ⌋ \textit{endIndex}_1 = \textit{index}_1 + \Big\lfloor \dfrac{k - 1}{2} \Big\rfloor endIndex1=index1+⌊2k−1⌋, endIndex 2 = index 2 + ⌊ k − 1 2 ⌋ \textit{endIndex}_2 = \textit{index}_2 + \Big\lfloor \dfrac{k - 1}{2} \Big\rfloor endIndex2=index2+⌊2k−1⌋。考虑 nums 1 [ endIndex 1 ] \textit{nums}_1[\textit{endIndex}_1] nums1[endIndex1] 和 nums 2 [ endIndex 2 ] \textit{nums}_2[\textit{endIndex}_2] nums2[endIndex2],其中的较大值是第 k k k 小元素(当 k k k 是奇数时)或第 k − 1 k - 1 k−1 小元素(当 k k k 是偶数时),因此其中的较小值一定不是第 k k k 小元素。对于较小值所在的数组,可以将较小值以及较小值前面的元素全部排除。
需要注意的是, endIndex 1 \textit{endIndex}_1 endIndex1 和 endIndex 2 \textit{endIndex}_2 endIndex2 不能超出数组下标范围。如果一个数组的剩余元素个数少于 ⌈ k 2 ⌉ \Big\lceil \dfrac{k}{2} \Big\rceil ⌈2k⌉,则该数组中考虑的元素是该数组中的全部剩余元素。因此有 endIndex 1 = min ( index 1 + ⌊ k − 1 2 ⌋ , m − 1 ) \textit{endIndex}_1 = \min(\textit{index}_1 + \Big\lfloor \dfrac{k - 1}{2} \Big\rfloor, m - 1) endIndex1=min(index1+⌊2k−1⌋,m−1), endIndex 2 = min ( index 2 + ⌊ k − 1 2 ⌋ , n − 1 ) \textit{endIndex}_2 = \min(\textit{index}_2 + \Big\lfloor \dfrac{k - 1}{2} \Big\rfloor, n - 1) endIndex2=min(index2+⌊2k−1⌋,n−1)。
由此可以根据三种情况分别做相应的处理,缩小查找范围。
-
如果 nums 1 [ endIndex 1 ] < nums 2 [ endIndex 2 ] \textit{nums}_1[\textit{endIndex}_1] < \textit{nums}_2[\textit{endIndex}_2] nums1[endIndex1]<nums2[endIndex2],则将 nums 1 \textit{nums}_1 nums1 的下标范围 [ index 1 , endIndex 1 ] [\textit{index}_1, \textit{endIndex}_1] [index1,endIndex1] 中的元素全部排除,排除的元素个数是 endIndex 1 − index 1 + 1 \textit{endIndex}_1 - \textit{index}_1 + 1 endIndex1−index1+1。
-
如果 nums 1 [ endIndex 1 ] > nums 2 [ endIndex 2 ] \textit{nums}_1[\textit{endIndex}_1] > \textit{nums}_2[\textit{endIndex}_2] nums1[endIndex1]>nums2[endIndex2],则将 nums 2 \textit{nums}_2 nums2 的下标范围 [ index 2 , endIndex 2 ] [\textit{index}_2, \textit{endIndex}_2] [index2,endIndex2] 中的元素全部排除,排除的元素个数是 endIndex 2 − index 2 + 1 \textit{endIndex}_2 - \textit{index}_2 + 1 endIndex2−index2+1。
-
如果 nums 1 [ endIndex 1 ] = nums 2 [ endIndex 2 ] \textit{nums}_1[\textit{endIndex}_1] = \textit{nums}_2[\textit{endIndex}_2] nums1[endIndex1]=nums2[endIndex2],则处理方式和 nums 1 [ endIndex 1 ] < nums 2 [ endIndex 2 ] \textit{nums}_1[\textit{endIndex}_1] < \textit{nums}_2[\textit{endIndex}_2] nums1[endIndex1]<nums2[endIndex2] 相同。
每次查找之后,将 k k k 的值减去排除的元素个数,并将排除元素的数组的相应下标更新为该数组首个剩余元素的下标,具体做法如下:如果排除的是 nums 1 \textit{nums}_1 nums1 中的元素,则将 index 1 \textit{index}_1 index1 更新为 endIndex 1 + 1 \textit{endIndex}_1 + 1 endIndex1+1;如果排除的是 nums 2 \textit{nums}_2 nums2 中的元素,则将 index 2 \textit{index}_2 index2 更新为 endIndex 2 + 1 \textit{endIndex}_2 + 1 endIndex2+1。
二分查找的条件是 index 1 < m \textit{index}_1 < m index1<m, index 2 < n \textit{index}_2 < n index2<n 和 k > 0 k > 0 k>0。如果三个条件之一不满足,则二分查找结束,得到目标值。
-
如果 index 1 = m \textit{index}_1 = m index1=m,则剩余元素都在 nums 2 \textit{nums}_2 nums2 中,目标值是 nums 2 [ index 2 + k ] \textit{nums}_2[\textit{index}_2 + k] nums2[index2+k]。
-
如果 index 2 = n \textit{index}_2 = n index2=n,则剩余元素都在 nums 1 \textit{nums}_1 nums1 中,目标值是 nums 1 [ index 1 + k ] \textit{nums}_1[\textit{index}_1 + k] nums1[index1+k]。
-
如果 k = 0 k = 0 k=0,则剩余元素中的最小元素是目标值,目标值是 min ( nums 1 [ index 1 ] , nums 2 [ index 2 ] ) \min(\textit{nums}_1[\textit{index}_1], \textit{nums}_2[\textit{index}_2]) min(nums1[index1],nums2[index2])。
以下用一个例子说明该解法。
两个数组是 nums 1 = [ 1 , 2 , 3 , 4 , 5 ] \textit{nums}_1 = [1, 2, 3, 4, 5] nums1=[1,2,3,4,5], nums 2 = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ] \textit{nums}_2 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] nums2=[1,2,3,4,5,6,7,8,9,10],两个数组的长度分别是 m = 5 m = 5 m=5, n = 10 n = 10 n=10,长度之和是 15 15 15, k = 7 k = 7 k=7。初始时, index 1 = 0 \textit{index}_1 = 0 index1=0, index 2 = 0 \textit{index}_2 = 0 index2=0。
-
根据 index 1 = 0 \textit{index}_1 = 0 index1=0, index 2 = 0 \textit{index}_2 = 0 index2=0 和 k = 7 k = 7 k=7 计算得到 endIndex 1 = 3 \textit{endIndex}_1 = 3 endIndex1=3, endIndex 2 = 3 \textit{endIndex}_2 = 3 endIndex2=3。由于 nums 1 [ 3 ] = nums 2 [ 3 ] \textit{nums}_1[3] = \textit{nums}_2[3] nums1[3]=nums2[3],因此将 nums 1 \textit{nums}_1 nums1 的下标范围 [ 0 , 3 ] [0, 3] [0,3] 排除,排除 4 4 4 个元素,更新得到 k = 3 k = 3 k=3, index 1 = 4 \textit{index}_1 = 4 index1=4。
-
根据 index 1 = 4 \textit{index}_1 = 4 index1=4, index 2 = 0 \textit{index}_2 = 0 index2=0 和 k = 3 k = 3 k=3 计算得到 endIndex 1 = 4 \textit{endIndex}_1 = 4 endIndex1=4, endIndex 2 = 1 \textit{endIndex}_2 = 1 endIndex2=1。由于 nums 1 [ 4 ] > nums 2 [ 1 ] \textit{nums}_1[4] > \textit{nums}_2[1] nums1[4]>nums2[1],因此将 nums 2 \textit{nums}_2 nums2 的下标范围 [ 0 , 1 ] [0, 1] [0,1] 排除,排除 2 2 2 个元素,更新得到 k = 1 k = 1 k=1, index 2 = 2 \textit{index}_2 = 2 index2=2。
-
根据 index 1 = 4 \textit{index}_1 = 4 index1=4, index 2 = 2 \textit{index}_2 = 2 index2=2 和 k = 1 k = 1 k=1 计算得到 endIndex 1 = 4 \textit{endIndex}_1 = 4 endIndex1=4, endIndex 2 = 2 \textit{endIndex}_2 = 2 endIndex2=2。由于 nums 1 [ 4 ] > nums 2 [ 2 ] \textit{nums}_1[4] > \textit{nums}_2[2] nums1[4]>nums2[2],因此将 nums 2 \textit{nums}_2 nums2 的下标范围 [ 2 , 2 ] [2, 2] [2,2] 排除,排除 1 1 1 个元素,更新得到 k = 0 k = 0 k=0, index 2 = 3 \textit{index}_2 = 3 index2=3。
-
此时 k = 0 k = 0 k=0,二分查找结束, nums 1 [ 4 ] \textit{nums}_1[4] nums1[4] 和 nums 2 [ 3 ] \textit{nums}_2[3] nums2[3] 中的较小值 4 4 4 即为目标值。
代码
class Solution {public double findMedianSortedArrays(int[] nums1, int[] nums2) {int m = nums1.length, n = nums2.length;int total = m + n;if (total % 2 == 1) {int medianIndex = (total - 1) / 2;return findKthSmallest(medianIndex, nums1, nums2);} else {int medianIndex1 = total / 2 - 1, medianIndex2 = total / 2;return (findKthSmallest(medianIndex1, nums1, nums2) + findKthSmallest(medianIndex2, nums1, nums2)) / 2.0;}}public int findKthSmallest(int k, int[] nums1, int[] nums2) {int m = nums1.length, n = nums2.length;int index1 = 0, index2 = 0;while (index1 < m && index2 < n && k > 0) {int endIndex1 = Math.min(index1 + (k - 1) / 2, m - 1);int endIndex2 = Math.min(index2 + (k - 1) / 2, n - 1);int num1 = nums1[endIndex1], num2 = nums2[endIndex2];if (num1 <= num2) {k -= endIndex1 - index1 + 1;index1 = endIndex1 + 1;} else {k -= endIndex2 - index2 + 1;index2 = endIndex2 + 1;}}if (index1 == m) {return nums2[index2 + k];} else if (index2 == n) {return nums1[index1 + k];} else {return Math.min(nums1[index1], nums2[index2]);}}
}
复杂度分析
-
时间复杂度: O ( log ( m + n ) ) O(\log (m + n)) O(log(m+n)),其中 m m m 和 n n n 分别是数组 nums 1 \textit{nums}_1 nums1 和 nums 2 \textit{nums}_2 nums2 的长度。每次寻找第 k k k 小元素时, k k k 的初始值是 m + n m + n m+n 的一半附近的整数,每次查找将 k k k 的值减小一半,因此时间复杂度是 O ( log ( m + n ) ) O(\log (m + n)) O(log(m+n))。
-
空间复杂度: O ( 1 ) O(1) O(1)。
解法二
思路和算法
解法一的时间复杂度是 O ( log ( m + n ) ) O(\log (m + n)) O(log(m+n)),该时间复杂度已经很低,但是这道题还存在时间复杂度更低的解法。
为了找到中位数,需要在数组 nums 1 \textit{nums}_1 nums1 和 nums 2 \textit{nums}_2 nums2 中分别找到分割点 cut 1 \textit{cut}_1 cut1 和 cut 2 \textit{cut}_2 cut2,将每个数组分割成两个部分。
-
数组 nums 1 \textit{nums}_1 nums1 被分割成下标范围 [ 0 , cut 1 − 1 ] [0, \textit{cut}_1 - 1] [0,cut1−1] 和下标范围 [ cut 1 , m − 1 ] [\textit{cut}_1, m - 1] [cut1,m−1] 两部分,左边部分的长度是 cut 1 \textit{cut}_1 cut1。
-
数组 nums 2 \textit{nums}_2 nums2 被分割成下标范围 [ 0 , cut 2 − 1 ] [0, \textit{cut}_2 - 1] [0,cut2−1] 和下标范围 [ cut 2 , n − 1 ] [\textit{cut}_2, n - 1] [cut2,n−1] 两部分,左边部分的长度是 cut 2 \textit{cut}_2 cut2。
其中, 0 ≤ cut 1 ≤ m 0 \le \textit{cut}_1 \le m 0≤cut1≤m, 0 ≤ cut 2 ≤ n 0 \le \textit{cut}_2 \le n 0≤cut2≤n,即每个数组分割成的两个部分中可以有一个部分为空。
假设 nums 1 [ − 1 ] = nums 2 [ − 1 ] = − ∞ \textit{nums}_1[-1] = \textit{nums}_2[-1] = -\infty nums1[−1]=nums2[−1]=−∞, nums 1 [ m ] = nums 2 [ n ] = + ∞ \textit{nums}_1[m] = \textit{nums}_2[n] = +\infty nums1[m]=nums2[n]=+∞,分割应满足以下两个条件。
-
两个数组的左边部分的最大值小于等于两个数组的右边部分的最小值, max ( nums 1 [ cut 1 − 1 ] , nums 2 [ cut 2 − 1 ] ) ≤ min ( nums 1 [ cut 1 ] , nums 2 [ cut 2 ] ) \max(\textit{nums}_1[\textit{cut}_1 - 1], \textit{nums}_2[\textit{cut}_2 - 1]) \le \min(\textit{nums}_1[\textit{cut}_1], \textit{nums}_2[\textit{cut}_2]) max(nums1[cut1−1],nums2[cut2−1])≤min(nums1[cut1],nums2[cut2])。
-
两个数组的左边部分的长度之和为两个数组的长度之和的一半向上取整, cut 1 + cut 2 = ⌈ m + n 2 ⌉ \textit{cut}_1 + \textit{cut}_2 = \Big\lceil \dfrac{m + n}{2} \Big\rceil cut1+cut2=⌈2m+n⌉。
将两个数组的左边部分统称为前半部分,将两个数组的右边部分统称为后半部分,则前半部分的最大值小于等于后半部分的最小值,前半部分的元素个数为两个数组的长度之和的一半向上取整。
用 total = m + n \textit{total} = m + n total=m+n 表示两个升序数组的长度之和,用 lowerSize = ⌈ total 2 ⌉ \textit{lowerSize} = \Big\lceil \dfrac{\textit{total}}{2} \Big\rceil lowerSize=⌈2total⌉ 表示前半部分的元素个数。当 total \textit{total} total 是奇数时,中位数是前半部分的最大值;当 total \textit{total} total 是偶数时,中位数是前半部分的最大值与后半部分的最小值的平均数。
由于已知 cut 1 + cut 2 = lowerSize \textit{cut}_1 + \textit{cut}_2 = \textit{lowerSize} cut1+cut2=lowerSize,因此可以在 nums 1 \textit{nums}_1 nums1 中寻找 cut 1 \textit{cut}_1 cut1,当 cut 1 \textit{cut}_1 cut1 确定之后 cut 2 \textit{cut}_2 cut2 也可以确定。
寻找 cut 1 \textit{cut}_1 cut1 可以使用二分查找实现。由于两个数组都是升序数组, nums 1 [ cut 1 − 1 ] ≤ nums 1 [ cut 1 ] \textit{nums}_1[\textit{cut}_1 - 1] \le \textit{nums}_1[\textit{cut}_1] nums1[cut1−1]≤nums1[cut1] 和 nums 2 [ cut 2 − 1 ] ≤ nums 2 [ cut 2 ] \textit{nums}_2[\textit{cut}_2 - 1] \le \textit{nums}_2[\textit{cut}_2] nums2[cut2−1]≤nums2[cut2] 都满足,因此只需要满足 nums 1 [ cut 1 − 1 ] ≤ nums 2 [ cut 2 ] \textit{nums}_1[\textit{cut}_1 - 1] \le \textit{nums}_2[\textit{cut}_2] nums1[cut1−1]≤nums2[cut2] 和 nums 2 [ cut 2 − 1 ] ≤ nums 1 [ cut 1 ] \textit{nums}_2[\textit{cut}_2 - 1] \le \textit{nums}_1[\textit{cut}_1] nums2[cut2−1]≤nums1[cut1] 即可。二分查找需要查找满足 nums 1 [ cut 1 − 1 ] ≤ nums 2 [ cut 2 ] \textit{nums}_1[\textit{cut}_1 - 1] \le \textit{nums}_2[\textit{cut}_2] nums1[cut1−1]≤nums2[cut2] 的最大下标 cut 1 \textit{cut}_1 cut1。
用 low \textit{low} low 和 high \textit{high} high 分别表示二分查找的下标范围的下界和上界,初始时 low = 0 \textit{low} = 0 low=0, high = m \textit{high} = m high=m。每次查找时,取 index 1 \textit{index}_1 index1 为 low \textit{low} low 和 high \textit{high} high 的平均数向上取整,并得到 index 2 = lowerSize − index 1 \textit{index}_2 = \textit{lowerSize} - \textit{index}_1 index2=lowerSize−index1,比较 nums 1 [ index 1 − 1 ] \textit{nums}_1[\textit{index}_1 - 1] nums1[index1−1] 和 nums 2 [ index 2 ] \textit{nums}_2[\textit{index}_2] nums2[index2] 的大小关系,调整查找的下标范围。
-
如果 nums 1 [ index 1 − 1 ] ≤ nums 2 [ index 2 ] \textit{nums}_1[\textit{index}_1 - 1] \le \textit{nums}_2[\textit{index}_2] nums1[index1−1]≤nums2[index2],则 cut 1 ≥ index 1 \textit{cut}_1 \ge \textit{index}_1 cut1≥index1,因此在下标范围 [ index 1 , high ] [\textit{index}_1, \textit{high}] [index1,high] 中继续查找。
-
如果 nums 1 [ index 1 − 1 ] > nums 2 [ index 2 ] \textit{nums}_1[\textit{index}_1 - 1] > \textit{nums}_2[\textit{index}_2] nums1[index1−1]>nums2[index2],则 cut 1 < index 1 \textit{cut}_1 < \textit{index}_1 cut1<index1,因此在下标范围 [ low , index 1 − 1 ] [\textit{low}, \textit{index}_1 - 1] [low,index1−1] 中继续查找。
当 low = high \textit{low} = \textit{high} low=high 时,查找结束,此时 low \textit{low} low 即为 cut 1 \textit{cut}_1 cut1。
得到 cut 1 \textit{cut}_1 cut1 之后即可得到 cut 2 \textit{cut}_2 cut2, nums 1 [ cut 1 − 1 ] \textit{nums}_1[\textit{cut}_1 - 1] nums1[cut1−1] 和 nums 2 [ cut 2 − 1 ] \textit{nums}_2[\textit{cut}_2 - 1] nums2[cut2−1] 中的最大值是前半部分的最大值, nums 1 [ cut 1 ] \textit{nums}_1[\textit{cut}_1] nums1[cut1] 和 nums 2 [ cut 2 ] \textit{nums}_2[\textit{cut}_2] nums2[cut2] 中的最小值是后半部分的最小值。根据前半部分的最大值和后半部分的最小值即可计算中位数。
-
当 total \textit{total} total 是奇数时,中位数是前半部分的最大值。
-
当 total \textit{total} total 是偶数时,中位数是前半部分的最大值与后半部分的最小值的平均数。
该解法的时间复杂度是 O ( log m ) O(\log m) O(logm),优于解法一的 O ( log ( m + n ) ) O(\log (m + n)) O(log(m+n))。
实现方面,由于只需要在一个数组中二分查找,因此可以选择较短的数组二分查找,时间复杂度是 O ( log min ( m , n ) ) O(\log \min(m, n)) O(logmin(m,n))。
以下用一个例子说明上述过程。
两个数组是 nums 1 = [ 1 , 2 , 3 , 4 , 5 ] \textit{nums}_1 = [1, 2, 3, 4, 5] nums1=[1,2,3,4,5], nums 2 = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ] \textit{nums}_2 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] nums2=[1,2,3,4,5,6,7,8,9,10],两个数组的长度分别是 m = 5 m = 5 m=5, n = 10 n = 10 n=10,长度之和是 15 15 15,前半部分的元素个数是 8 8 8。初始时, low = 0 \textit{low} = 0 low=0, high = 5 \textit{high} = 5 high=5。
-
根据 low = 0 \textit{low} = 0 low=0 和 high = 5 \textit{high} = 5 high=5 计算得到 index 1 = 3 \textit{index}_1 = 3 index1=3, index 2 = 5 \textit{index}_2 = 5 index2=5。由于 nums 1 [ 2 ] ≤ nums 2 [ 5 ] \textit{nums}_1[2] \le \textit{nums}_2[5] nums1[2]≤nums2[5],因此将 low \textit{low} low 更新为 3 3 3。
-
根据 low = 3 \textit{low} = 3 low=3 和 high = 5 \textit{high} = 5 high=5 计算得到 index 1 = 4 \textit{index}_1 = 4 index1=4, index 2 = 4 \textit{index}_2 = 4 index2=4。由于 nums 1 [ 3 ] ≤ nums 2 [ 4 ] \textit{nums}_1[3] \le \textit{nums}_2[4] nums1[3]≤nums2[4],因此将 low \textit{low} low 更新为 4 4 4。
-
根据 low = 4 \textit{low} = 4 low=4 和 high = 5 \textit{high} = 5 high=5 计算得到 index 1 = 5 \textit{index}_1 = 5 index1=5, index 2 = 3 \textit{index}_2 = 3 index2=3。由于 nums 1 [ 4 ] > nums 2 [ 3 ] \textit{nums}_1[4] > \textit{nums}_2[3] nums1[4]>nums2[3],因此将 high \textit{high} high 更新为 4 4 4。
-
此时 low = high \textit{low} = \textit{high} low=high,二分查找结束。根据 low = 4 \textit{low} = 4 low=4 计算得到 cut 1 = 4 \textit{cut}_1 = 4 cut1=4, cut 2 = 4 \textit{cut}_2 = 4 cut2=4,前半部分的最大值是 4 4 4,后半部分的最小值是 5 5 5。由于两个数组的长度之和是奇数,因此中位数是前半部分的最大值,中位数是 4 4 4。
代码
class Solution {public double findMedianSortedArrays(int[] nums1, int[] nums2) {return nums1.length <= nums2.length ? findMedian(nums1, nums2) : findMedian(nums2, nums1);}public double findMedian(int[] shorter, int[] longer) {int length1 = shorter.length, length2 = longer.length;int total = length1 + length2;int lowerSize = (total + 1) / 2;int low = 0, high = length1;while (low < high) {int index1 = low + (high - low + 1) / 2;int index2 = lowerSize - index1;int left1 = shorter[index1 - 1];int right2 = longer[index2];if (left1 <= right2) {low = index1;} else {high = index1 - 1;}}int cut1 = low, cut2 = lowerSize - low;int lower1 = cut1 == 0 ? Integer.MIN_VALUE : shorter[cut1 - 1];int lower2 = cut2 == 0 ? Integer.MIN_VALUE : longer[cut2 - 1];int higher1 = cut1 == length1 ? Integer.MAX_VALUE : shorter[cut1];int higher2 = cut2 == length2 ? Integer.MAX_VALUE : longer[cut2];int lowerMax = Math.max(lower1, lower2), higherMin = Math.min(higher1, higher2);if (total % 2 == 1) {return lowerMax;} else {return (lowerMax + higherMin) / 2.0;}}
}
复杂度分析
-
时间复杂度: O ( log min ( m , n ) ) O(\log \min(m, n)) O(logmin(m,n)),其中 m m m 和 n n n 分别是数组 nums 1 \textit{nums}_1 nums1 和 nums 2 \textit{nums}_2 nums2 的长度。在较短的数组中二分查找,范围是 [ 0 , min ( m , n ) ] [0, \min(m, n)] [0,min(m,n)],二分查找的次数是 O ( log min ( m , n ) ) O(\log \min(m, n)) O(logmin(m,n)),每次查找的时间是 O ( 1 ) O(1) O(1),因此时间复杂度是 O ( log min ( m , n ) ) O(\log \min(m, n)) O(logmin(m,n))。
-
空间复杂度: O ( 1 ) O(1) O(1)。
相关文章:
二分查找题目:寻找两个正序数组的中位数
文章目录 题目标题和出处难度题目描述要求示例数据范围 解法一思路和算法代码复杂度分析 解法二思路和算法代码复杂度分析 题目 标题和出处 标题:寻找两个正序数组的中位数 出处:4. 寻找两个正序数组的中位数 难度 8 级 题目描述 要求 给定两个大…...
【技术洞察】2024科技绘卷:浪潮、突破、未来
涌动与突破 2024年,科技的浪潮汹涌澎湃,人工智能、量子计算、脑机接口等前沿技术如同璀璨星辰,方便了大家的日常生活,也照亮了人类未来的道路。这一年,科技的突破与创新不断刷新着人们对未来的想象。那么回顾2024年的科…...
【Linux】gdb——Linux调试器
gdb使用背景 程序的发布方式有两种,debug模式和release模式 Linux gcc/g出来的二进制程序,默认是release模式 要使用gdb调试,必须在源代码生成二进制程序的时候, 加上 -g 选项 gdb使用方法 首先进入gdb gdb test_glist显示代码 断点 b 行…...
fpga系列 HDL:XILINX Vivado Vitis 高层次综合(HLS) 实现 EBAZ板LED控制(上)
目录 创建工程创建源文件并编写C代码C仿真综合仿真导出RTL CG导出RTL错误处理: 创建工程 创建源文件并编写C代码 创建源文件(Souces下的hlsv.h和hlsv.cpp,Test Bench下的test_hlsv1.cpp): hlsv1.h #ifndef HLSV1 #define HLSV1 #include &l…...
卡特兰数学习
1,概念 卡特兰数(英语:Catalan number),又称卡塔兰数,明安图数。是组合数学中一种常出现于各种计数问题中的数列。它在不同的计数问题中频繁出现。 2,公式 卡特兰数的递推公式为:f(…...
度小满Java开发面试题及参考答案 (上)
String 是基本类型吗?String、StringBuffer、StringBuilder 的区别是什么?拼接字符串有哪些做法? String 不是基本类型,它是 Java 中的一个类,属于引用类型。 下面来看看 String、StringBuffer、StringBuilder 的区别: 类型可变性线程安全性性能适用场景String不可变线程…...
Python-基于PyQt5,json和playsound的通用闹钟
前言:刚刚结束2024年秋季学期的学习,接下来我们继续来学习PyQt5。由于之前我们已经学习了PyQt5以及PyUIC,Pyrcc和QtDesigner的安装,配置。所以接下来我们一起深入PyQt5,学习如何利用PyQt5进行实际开发-基于PyQt5,json和…...
关于数字地DGND和模拟地AGND隔离
文章目录 前言一、1、为什么要进行数字地和模拟地隔离二、隔离元件1.①0Ω电阻:2.②磁珠:3.电容:4.④电感: 三、隔离方法①单点接地②数字地与模拟地分开布线,最后再PCB板上一点接到电源。③电源隔离④、其他隔离方法 …...
小识Java死锁是否会造成CPU100%?
死锁或者大量的死锁不一定会直接导致CPU占用率达到100%。以下是详细分析: 一、死锁对CPU的影响 资源占用:死锁是指两个或多个线程(或进程)在相互等待对方释放资源,导致所有涉及的线程都无法继续执行。在死锁状态下&a…...
DeepSeek R1学习
0.回顾: https://blog.csdn.net/Together_CZ/article/details/144431432?ops_request_misc%257B%2522request%255Fid%2522%253A%25226574a586f0850d0329fbb720e5b8d5a9%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id…...
激光线扫相机无2D图像的标定方案
方案一:基于运动控制平台的标定 适用场景:若激光线扫相机安装在可控运动平台(如机械臂、平移台、旋转台)上,且平台的运动精度已知(例如通过编码器或高精度步进电机控制)。 步骤: 标…...
12 款开源OCR发 PDF 识别框架
2024 年 12 款开源文档解析框架的选型对比评测:PDF解析、OCR识别功能解读、应用场景分析及优缺点比较 这是该系列的第二篇文章,聚焦于智能文档处理(特别是 PDF 解析)。无论是在模型预训练的数据收集阶段,还是基于 RAG…...
【反悔堆】【hard】力扣871. 最低加油次数
汽车从起点出发驶向目的地,该目的地位于出发位置东面 target 英里处。 沿途有加油站,用数组 stations 表示。其中 stations[i] [positioni, fueli] 表示第 i 个加油站位于出发位置东面 positioni 英里处,并且有 fueli 升汽油。 假设汽车油…...
为什么应用程序是特定于操作系统的?[计算机原理]
你把WINDOWS程序复制到MAC上使用,会发现无法运行。你可能会说,MAC是arm处理器,而WINDWOS是X86 处理器。但是在2019年,那时候MAC电脑还全是Intel处理器,在同样的X86芯片上,运行MAC和WINDOWS 程序还是无法互相…...
多项日常使用测试,带你了解如何选择AI工具 Deepseek VS ChatGpt VS Claude
多项日常使用测试,带你了解如何选择AI工具 Deepseek VS ChatGpt VS Claude 注:因为考虑到绝大部分人的使用,我这里所用的模型均为免费模型。官方可访问的。ChatGPT这里用的是4o Ai对话,编程一直以来都是人们所讨论的话题。Ai的出现…...
什么是循环神经网络?
一、概念 循环神经网络(Recurrent Neural Network, RNN)是一类用于处理序列数据的神经网络。与传统的前馈神经网络不同,RNN具有循环连接,可以利用序列数据的时间依赖性。正因如此,RNN在自然语言处理、时间序列预测、语…...
Flink运行时架构
一、系统架构 1)作业管理器(JobManager) JobManager是一个Flink集群中任务管理和调度的核心,是控制应用执行的主进程。也就是说,每个应用都应该被唯一的JobManager所控制执行。 JobManger又包含3个不同的组件。 &am…...
网络工程师 (6)操作系统概述
一、操作系统的定义 (一)基本定义 操作系统(Operating System,简称OS)是计算机系统中至关重要的基础性系统软件。它是计算机硬件与上层软件之间的桥梁,负责管理和控制整个计算机系统的硬件和软件资源&…...
【2025年数学建模美赛C题】第1-5问F奖解题思路+高级绘图+可运行代码
基于多模型分析的奥运会奖牌预测与影响因素研究 解题思路一、问题重述二、问题分析三、模型假设与符号说明四、数据预处理五、奖牌榜预测5.1 基于LSTM长短期记忆循环神经网络的预测模型的建立5.2 模型预测结果 六、首枚奖牌预测6.1 BP神经网络的建立6.2 模型预测结果 七、各国奖…...
StarRocks 安装部署
StarRocks 安装部署 StarRocks端口: 官方《配置检查》有服务端口详细描述: https://docs.starrocks.io/zh/docs/deployment/environment_configurations/ StarRocks架构:https://docs.starrocks.io/zh/docs/introduction/Architecture/ Sta…...
RoboMaster- RDK X5能量机关实现案例(一)识别
作者:SkyXZ CSDN:https://blog.csdn.net/xiongqi123123 博客园:https://www.cnblogs.com/SkyXZ 在RoboMaster的25赛季,我主要负责了能量机关的视觉方案开发,目前整体算法已经搭建完成,实际方案上我使用的上…...
llama.cpp LLM_ARCH_DEEPSEEK and LLM_ARCH_DEEPSEEK2
llama.cpp LLM_ARCH_DEEPSEEK and LLM_ARCH_DEEPSEEK2 1. LLM_ARCH_DEEPSEEK and LLM_ARCH_DEEPSEEK22. LLM_ARCH_DEEPSEEK and LLM_ARCH_DEEPSEEK23. struct ggml_cgraph * build_deepseek() and struct ggml_cgraph * build_deepseek2()References 不宜吹捧中国大语言模型的同…...
检测到联想鼠标自动调出运行窗口,鼠标自己作为键盘操作
联想鼠标会自动时不时的调用“运行”窗口 然后鼠标自己作为键盘输入 然后打开这个网页 (不是点击了什么鼠标外加按键,这个鼠标除了左右和中间滚轮,没有其他按键了)...
-bash: ./uninstall.command: /bin/sh^M: 坏的解释器: 没有那个文件或目录
终端报错: -bash: ./uninstall.command: /bin/sh^M: 坏的解释器: 没有那个文件或目录原因:由于文件行尾符不匹配导致的。当脚本文件在Windows环境中创建或编辑后,行尾符为CRLF(即回车和换行,\r\n)…...
15天基础内容总复习
总复习 一.day01内容 1.JVM,JRE,JDK的关系 JVM: java虚拟机,用来运行java程序的,JVM本身是不夸平台的,每个操作系统都需要安装针对本操作系统的JVM所以: java通过jvm的不夸平台实现了java的跨平台JRE:java运行环境,包含jvm和核心类库JDK:java开发工具包,包含开发工具和JRE三…...
星火大模型接入及文本生成HTTP流式、非流式接口(JAVA)
文章目录 一、接入星火大模型二、基于JAVA实现HTTP非流式接口1.配置2.接口实现(1)分析接口请求(2)代码实现 3.功能测试(1)测试对话功能(2)测试记住上下文功能 三、基于JAVA实现HTTP流…...
如何将电脑桌面默认的C盘设置到D盘?详细操作步骤!
将电脑桌面默认的C盘设置到D盘的详细操作步骤! 本博文介绍如何将电脑桌面(默认为C盘)设置在D盘下。 首先,在D盘建立文件夹Desktop,完整的路径为D:\Desktop。winR,输入Regedit命令。(或者单击【…...
toRow和markRow的用法以及使用场景
Vue3 Raw API 完整指南 1. toRaw vs markRaw 1.1 基本概念 toRaw: 返回响应式对象的原始对象,用于临时获取原始数据结构,标记过后将会失去响应式markRaw: 标记一个对象永远不会转换为响应式对象,返回对象本身 1.2 使用对比 // toRaw 示例…...
Java中ExecutorService接口介绍、应用场景和示例代码
概述 ExecutorService 是 Java 中用于管理线程池的接口,它属于 java.util.concurrent 包。它提供了用于管理并发任务的功能,包括任务的提交、执行和线程池的生命周期管理。以下是对 ExecutorService 的详细讲解、应用场景和示例代码。 1. 详细讲解 1.…...
java 判断Date是上午还是下午
我要用Java生成表格统计信息,如下图所示: 所以就诞生了本文的内容。 在 Java 里,判断 Date 对象代表的时间是上午还是下午有多种方式,下面为你详细介绍不同的实现方法。 方式一:使用 java.util.Calendar Calendar 类…...
