二分查找题目:寻找两个正序数组的中位数
文章目录
- 题目
- 标题和出处
- 难度
- 题目描述
- 要求
- 示例
- 数据范围
- 解法一
- 思路和算法
- 代码
- 复杂度分析
- 解法二
- 思路和算法
- 代码
- 复杂度分析
题目
标题和出处
标题:寻找两个正序数组的中位数
出处: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 级 题目描述 要求 给定两个大…...
Java Web-Tomcat Servlet
Web服务器-Tomcat Web服务器简介 Web 服务器是一种软件程序,它主要用于在网络上接收和处理客户端(如浏览器)发送的 HTTP 请求,并返回相应的网页内容或数据。以下是关于 Web 服务器的详细介绍: 功能 接收请求&#…...
渗透测试-WAF是什么以及原理解释 waf功能详解
目录 waf功能介绍 waf出现的地点: 什么是waf 功能: 常见的系统攻击分为两类 一是利用Web服务器的漏洞进行攻击 二是利用网页自身的安全漏洞进行攻击 WAF主要功能: waf的特点1 waf主要功能2 网马木马主动防御及查杀 流量监控 网站漏洞防御功能 危险组件…...
Vue3 provide/inject用法总结
1. 基本概念 provide/inject 是 Vue3 中实现跨层级组件通信的方案,类似于 React 的 Context。它允许父组件向其所有子孙组件注入依赖,无论层级有多深。 1.1 基本语法 // 提供方(父组件) const value ref(hello) provide(key, …...
C# 提取PDF表单数据
目录 使用工具 C# 提取多个PDF表单域的数据 C# 提取特定PDF表单域的数据 PDF表单是一种常见的数据收集工具,广泛应用于调查问卷、业务合同等场景。凭借出色的跨平台兼容性和标准化特点,PDF表单在各行各业中得到了广泛应用。然而,当需要整合…...
【JAVA项目】基于ssm的【宠物医院信息管理系统】
【JAVA项目】基于ssm的【宠物医院信息管理系统】 技术简介:采用JSP技术、ssm框架、B/S架构、MySQL技术等实现。 系统简介:宠物医院信息管理系统,在系统首页可以查看首页、医学知识、医生信息、药品信息、新闻资讯、留言反馈、我的、跳转到后台…...
书生大模型实战营2
L0——入门岛 Python基础 Conda虚拟环境 虚拟环境是Python开发中不可或缺的一部分,它允许你在不同的项目中使用不同版本的库,避免依赖冲突。Conda是一个强大的包管理器和环境管理器。 创建新环境 首先,确保你已经安装了Anaconda或Minico…...
产业园管理系统提升企业综合管理效率与智能化水平的成功案例分析
内容概要 在当前科技迅猛发展的时代,越来越多的企业意识到数字化转型的重要性。为了提升管理效率和智能化水平,产业园管理系统应运而生,成为众多园区和商办写字楼不可或缺的一部分。无论是工业园、物流园还是公寓,这些系统都能为…...
《AI赋能光追:开启图形渲染新时代》
光线追踪技术是图形渲染领域的重大突破,能够通过模拟光的传播路径,精准渲染反射、折射、阴影和间接光照等效果,实现高度逼真的场景呈现。而人工智能的加入,更是为光线追踪技术带来了前所未有的变革,主要体现在以下几个…...
危机13小时:追踪一场GitHub投毒事件
事件概要 自北京时间 2024.12.4 晚间6点起, GitHub 上不断出现“幽灵仓库”,仓库中没有任何代码,只有诱导性的病毒文件。当天,他们成为了 GitHub 上 star 增速最快的仓库。超过 180 个虚假僵尸账户正在传播病毒,等待不…...
利用JSON数据类型优化关系型数据库设计
利用JSON数据类型优化关系型数据库设计 前言 在关系型数据库中,传统的结构化存储方式要求预先定义好所有的列及其数据类型。 然而,随着业务的发展,这种设计可能会显得不够灵活,尤其是在需要扩展单个列的描述功能时。 JSON数据…...
如何学习Java后端开发
文章目录 一、Java 语言基础二、数据库与持久层三、Web 开发基础四、主流框架与生态五、分布式与高并发六、运维与部署七、项目实战八、持续学习与提升总结路线图 学习 Java 后端开发需要系统性地掌握多个技术领域,从基础到进阶逐步深入。以下是一个详细的学习路线和…...
AI刷题-蛋糕工厂产能规划、优质章节的连续选择
挑两个简单的写写 目录 一、蛋糕工厂产能规划 问题描述 输入格式 输出格式 解题思路: 问题理解 数据结构选择 算法步骤 关键点 最终代码: 运行结果:编辑 二、优质章节的连续选择 问题描述 输入格式 输出格式 解题思路&a…...
SpringBoot统一数据返回格式 统一异常处理
统一数据返回格式 & 统一异常处理 1. 统一数据返回格式1.1 快速入门1.2 存在问题1.3 案列代码修改1.4 优点 2. 统一异常处理 1. 统一数据返回格式 强制登录案例中,我们共做了两部分⼯作 通过Session来判断⽤⼾是否登录对后端返回数据进⾏封装,告知前端处理的结果 回顾 后…...
[MySQL]事务的理论、属性与常见操作
目录 一、事物的理论 1.什么是事务 2.事务的属性(ACID) 3.再谈事务的本质 4.为什么要有事务 二、事务的操作 1.事务的支持版本 2.事务的提交模式 介绍 自动提交模式 手动提交模式 3.事务的操作 4.事务的操作演示 验证事务的回滚 事务异常…...
JWT实现单点登录
文章目录 JWT实现单点登录JWT 简介存在问题及解决方案登录流程后端程序实现前端保存Tokenstore存放信息的缺点及解决 校验流程:为gateway增加登录校验拦截器 另一种单点登录方法:Token+Redis实现单点登录 JWT实现单点登录 登录流程ÿ…...
docker 学习笔记
一、docker容器快速上手以及简单操作 docker的image和container image镜像 docker image就是一个read.only文件,可以理解成一个模版,docker image具有分层的概念 可以自己制作,也可以从registry拉去 container容器 一个运行中的docker …...
Lesson 119 A true story
Lesson 119 A true story 词汇 story n. 故事,传记,小说,楼层storey 搭配:tell a story 讲故事,说谎 true story 真实的故事 the second floor 二楼 例句:我猜他正在说谎。 I guess he…...
c语言版贪吃蛇(Pro Max版)附源代码
1 背景 贪吃蛇是一款经典的电子游戏,最早出现在20世纪70年代的街机游戏中。游戏的核心玩法是玩家控制一条蛇在有限的空间内移动,通过吃食物来增长身体长度,同时避免撞到墙壁、障碍物或自身。随着蛇的长度增加,游戏难度逐渐提升。 …...
蓝桥村打花结的花纸选择问题
在这篇文章中,我们将探讨一个有趣的算法问题,这个问题涉及到中国传统手工艺——打花结。我们需要判断给定的矩形花纸是否可以通过折叠操作使其面积变为特定的值 X,从而适合用来打花结。 问题描述 解题思路 这个问题可以通过循环方法来解决。…...
SSM开发(三) spring与mybatis整合(含完整运行demo源码)
目录 本文主要内容 一、Spring整合MyBatis的三个关键点 二、整合步骤 1、创建一个Maven项目 2、在pom.xml文件中添加jar包的依赖 3、配置MyBatis 注解实现方式 XML配置文件实现 4、配置Spring 5、测试运行 本文主要内容 1. Spring + Mybatis整合; 2. MyBatis两种SQL…...
【Matlab高端绘图SCI绘图模板】第006期 对比绘柱状图 (只需替换数据)
1. 简介 柱状图作为科研论文中常用的实验结果对比图,本文采用了3组实验对比的效果展示图,代码已调试好,只需替换数据即可生成相关柱状图,为科研加分。通过获得Nature配色的柱状图,让你的论文看起来档次更高࿰…...
Elasticsearch中的度量聚合:深度解析与实战应用
在大数据和实时分析日益重要的今天,Elasticsearch以其强大的搜索和聚合能力,成为了众多企业和开发者进行数据分析和处理的首选工具。本文将深入探讨Elasticsearch中的度量聚合(Metric Aggregations),展示其如何在数据分…...
重回C语言之老兵重装上阵(十六)C语言可变参数
C语言可变参数 在C语言中,标准库提供了一些函数允许接收可变数量的参数。最典型的例子就是 printf 和 scanf,它们能够处理不确定数量的参数。为了实现这一功能,C语言提供了可变参数函数的概念。 1. 可变参数函数的概念 可变参数函数是指函数…...
第4章 神经网络【1】——损失函数
4.1.从数据中学习 实际的神经网络中,参数的数量成千上万,因此,需要由数据自动决定权重参数的值。 4.1.1.数据驱动 数据是机器学习的核心。 我们的目标是要提取出特征量,特征量指的是从输入数据/图像中提取出的本质的数 …...
动态规划——斜率优化DP
题目清单 acwing300.任务安排1 状态表示f[i]: 集合:完成前i个任务且第i个任务为最后一个批次最后一个任务的方案。 属性:min 状态计算: f [ i ] m i n { f [ j ] s u m t [ i ] ∑ j 1 i w [ u ] s ∑ j 1 n w [ i ] } f[i]min\{f[j…...
函数栈帧的创建和销毁
1、总述: 大家在前期学习函数的时候,肯定会有诸多疑惑: 1、局部变量怎么创建的? 2、为什么有时候局部变量是随机值? 3、函数是怎么传参的?传参的顺序如何? 4、形参和实参是什么样的关系&am…...
【MQ】探索 Kafka
高性能 消息的顺序性、顺序写磁盘 零拷贝 RocketMQ内部主要是使用基于mmap实现的零拷贝,用来读写文件 减少cpu的拷贝次数和上下文切换次数,实现文件的高效读写操作 Kafka 零拷贝 Kafka 使用到了 mmap 和 sendfile 的方式来实现零拷贝。分别对应 Jav…...
c++ set/multiset 容器
1. set 基本概念 简介: 所有元素都会在插入时自动排序本质: set/multiset属于关联式容器,底层结构是用二叉树实现。set 和 multiset 区别: set容器不允许有重复的元素。 multiset允许有重复的元素。2. set 构造和赋值 构造&a…...
react-bn-面试
1.主要内容 工作台待办 实现思路: 1,待办list由后端返回,固定需要的字段有id(查详细)、type(本条待办的类型),还可能需要时间,状态等 2,一个集中处理待办中转路由页,所有待办都跳转到这个页面…...
【C++数论】880. 索引处的解码字符串|2010
本文涉及知识点 数论:质数、最大公约数、菲蜀定理 LeetCode880. 索引处的解码字符串 给定一个编码字符串 s 。请你找出 解码字符串 并将其写入磁带。解码时,从编码字符串中 每次读取一个字符 ,并采取以下步骤: 如果所读的字符是…...
shiro学习五:使用springboot整合shiro。在前面学习四的基础上,增加shiro的缓存机制,源码讲解:认证缓存、授权缓存。
文章目录 前言1. 直接上代码最后在讲解1.1 新增的pom依赖1.2 RedisCache.java1.3 RedisCacheManager.java1.4 jwt的三个类1.5 ShiroConfig.java新增Bean 2. 源码讲解。2.1 shiro 缓存的代码流程。2.2 缓存流程2.2.1 认证和授权简述2.2.2 AuthenticatingRealm.getAuthentication…...
Python案例--养兔子
兔子繁殖问题是一个经典的数学问题,最早由意大利数学家斐波那契在13世纪提出。这个问题不仅在数学领域具有重要意义,还广泛应用于计算机科学、生物学和经济学等领域。本文将通过一个具体的Python程序,深入探讨兔子繁殖问题的建模和实现&#…...
【搜索回溯算法】:BFS的魔力--如何使用广度优先搜索找到最短路径
✨感谢您阅读本篇文章,文章内容是个人学习笔记的整理,如果哪里有误的话还请您指正噢✨ ✨ 个人主页:余辉zmh–CSDN博客 ✨ 文章所属专栏:搜索回溯算法篇–CSDN博客 文章目录 一.广度优先搜索(BFS)解决最短路…...
JavaSE第十一天——集合框架Collection
一、List接口 List接口是一个有序的集合,允许元素有重复,它继承了Collection接口,提供了许多额外的功能,比如基于索引的插入、删除和访问元素等。 常见的List接口的实现类有ArrayList、LinkedList和Vector。 List接口的实现类 …...
Three城市引擎地图插件Geo-3d
一、简介 基于Three开发,为Three 3D场景提供GIS能力和城市底座渲染能力。支持Web墨卡托、WGS84、GCJ02等坐标系,支持坐标转换,支持影像、地形、geojson建筑、道路,植被等渲染。支持自定义主题。 二、效果 三、代码 //插件初始化…...
深度学习|表示学习|卷积神经网络|详细推导每一层的维度变化|14
如是我闻: 一个经典的卷积神经网络(CNN)架构,呈现的是输入图像通过多个卷积层、池化层以及全连接层,最终输出分类结果的过程。整个过程的核心是理解输入特征图的尺寸如何在每一层发生变化,我们可以通过卷积…...
多级缓存(亿级并发解决方案)
多级缓存(亿级流量(并发)的缓存方案) 传统缓存的问题 传统缓存是请求到达tomcat后,先查询redis,如果未命中则查询数据库,问题如下: (1)请求要经过tomcat处…...
BOM对象location与数组操作结合——查询串提取案例
BOM对象location与数组操作结合——查询串提取案例 前置知识 1. Location 对象 Location 对象是 JavaScript 提供的内置对象之一,它表示当前窗口或框架的 URL,并允许你通过它操作或获取 URL 的信息。可以通过 window.location 访问。 主要属性&#…...
读书笔记--分布式服务架构对比及优势
本篇是在上一篇的基础上,主要对共享服务平台建设所依赖的分布式服务架构进行学习,主要记录和思考如下,供大家学习参考。随着企业各业务数字化转型工作的推进,之前在传统的单一系统(或单体应用)模式中&#…...
GOGOGO 枚举
含义:一种类似于类的一种结构 作用:是Java提供的一个数据类型,可以设置值是固定的 【当某一个数据类型受自身限制的时候,使用枚举】 语法格式: public enum 枚举名{…… }有哪些成员? A、对象 public …...
【Linux】Linux基础开发工具
1 Linux 软件包管理器 yum 1.1软件包 在Linux下安装软件, 一个通常的办法是下载到程序的源代码, 并进行编译, 得到可执行程序. 但是这样太麻烦了, 于是有些人把一些常用的软件提前编译好, 做成软件包(可以理解成windows上的安装程序)放在一个服务器上,通过包管理器可以很方便的…...
嵌入式C语言:结构体的多态性之结构体中的void*万能指针
目录 一、void*指针在结构体中的应用 二、实现方式 2.1. 定义通用结构体 2.2. 定义具体结构体 2.3. 初始化和使用 三、应用场景 3.1. 内存管理函数 3.2. 泛型数据结构(链表) 3.3. 回调函数和函数指针 3.4. 跨语言调用或API接口(模拟…...
重构进行时:一秒告别 !=null 判空
重构进行时:一秒告别 !null 判空 空指针异常(NullPointerException)是Java开发中常见的错误之一。 许多开发者在遇到空指针问题时,往往会习惯性地使用! null来进行判断。 然而,当代码中频繁出现这种判断时ÿ…...
React 中hooks之useSyncExternalStore使用总结
1. 基本概念 useSyncExternalStore 是 React 18 引入的一个 Hook,用于订阅外部数据源,确保在并发渲染下数据的一致性。它主要用于: 订阅浏览器 API(如 window.width)订阅第三方状态管理库订阅任何外部数据源 1.1 基…...
Semaphore 与 线程池 Executor 有什么区别?
前言:笔者在看Semaphone时 突然脑子宕机,啥啥分不清 Semaphore 和 Executor 的作用,故再次记录。 一、什么是Semaphore? Semaphore 是 Java 并发编程(JUC)中一个重要的同步工具,它的作用是 控…...
Rust:高性能与安全并行的编程语言
引言 在现代编程世界里,开发者面临的最大挑战之一就是如何平衡性能与安全性。在许多情况下,C/C这样的系统级编程语言虽然性能强大,但其内存管理的复杂性导致了各种安全漏洞。为了解决这些问题,Rust 作为一种新的系统级编程语言进入…...
论文笔记(六十三)Understanding Diffusion Models: A Unified Perspective(六)(完结)
Understanding Diffusion Models: A Unified Perspective(六)(完结) 文章概括指导(Guidance)分类器指导无分类器引导(Classifier-Free Guidance) 总结 文章概括 引用: …...
Redis --- 分布式锁的使用
我们在上篇博客高并发处理 --- 超卖问题一人一单解决方案讲述了两种锁解决业务的使用方法,但是这样不能让锁跨JVM也就是跨进程去使用,只能适用在单体项目中如下图: 为了解决这种场景,我们就需要用一个锁监视器对全部集群进行监视…...
电脑怎么格式化?格式化详细步骤
格式化是我们在日常使用电脑时可能会用到的一种操作,无论是清理磁盘空间、安装新系统,还是解决磁盘读写错误,都可能需要格式化。不过,对于一些不熟悉电脑操作的用户来说,格式化听起来可能有些复杂。其实,只…...