4. 寻找两个正序数组的中位数:用分割点代替合并

题目

给定两个大小分别为 mn 的正序数组 nums1nums2,返回两个数组合并后的中位数。

题目要求时间复杂度为 O(log(m+n))

例如:

复制代码
nums1 = [1, 3]
nums2 = [2]

合并后:[1, 2, 3]
中位数:2

初始思路为什么不满足要求

把两个数组拼接起来,再排序,时间复杂度至少是 O((m+n)log(m+n))

即使使用双指针在线性时间内完成合并,也需要 O(m+n),仍然没有达到题目要求的对数复杂度。

因此,关键是不要真正构造合并后的数组,而是直接确定中位数左右两侧的边界。

核心观察:寻找两个分割点

把两个数组分别切成左右两部分:

复制代码
nums1: [左半边] | [右半边]
nums2: [左半边] | [右半边]

i 表示从 nums1 中取多少个元素放入左半边,j 表示从 nums2 中取多少个元素放入左半边。

如果总长度为 m+n,令:

复制代码
half = (m + n + 1) / 2

为了保证左半边元素数量正确,需要满足:

复制代码
i + j = half
j = half - i

这里的 ij 是元素数量,不是最后一个元素的下标。

例如:

复制代码
nums1 = [1, 4, 7, 10]
nums2 = [2, 3, 5, 6, 8, 9]
half = 5

如果 i=2,那么 j=3

复制代码
nums1: [1, 4]    | [7, 10]
nums2: [2, 3, 5] | [6, 8, 9]

左半边一共有 2+3=5 个元素。

有效分割点的条件

两个数组各自有序,所以只需要检查分割点附近的四个边界值:

复制代码
left1  = nums1[i - 1]   // nums1 左半边最大值
right1 = nums1[i]       // nums1 右半边最小值
left2  = nums2[j - 1]   // nums2 左半边最大值
right2 = nums2[j]       // nums2 右半边最小值

有效分割必须满足:

复制代码
left1 <= right2
left2 <= right1

这表示左半边的所有元素都不大于右半边的所有元素。

当分割点位于数组两端时,用无穷边界处理:

复制代码
i == 0:left1  = 负无穷
i == m:right1 = 正无穷
j == 0:left2  = 负无穷
j == n:right2 = 正无穷

为什么只二分较短的数组

先保证 nums1 是较短数组:

复制代码
if (nums1.length > nums2.length) {
    交换 nums1 和 nums2;
}

这样二分范围只有 [0,m],并且根据 j=half-i 推出的 j 一定落在 nums2 的合理范围内,同时可以避免较短数组的分割位置越界。

开区间二分

我们寻找第一个满足下面条件的 i

复制代码
nums1[i] >= nums2[j - 1]

使用开区间哨兵:

复制代码
int left = -1;
int right = m;

其中 left 表示条件不满足的位置,right 表示条件满足的位置或右侧哨兵。每轮计算:

复制代码
int i = left + (right - left) / 2;
int j = half - i;

然后根据 nums1[i]nums2[j-1] 的关系更新边界:

  • 如果 nums1[i] < nums2[j-1],说明 nums1 左半边取少了,i 应该增大:left = i
  • 否则当前 i 可能已经足够,继续向左寻找第一个满足条件的位置:right = i

循环结束时,right 就是最终的 i

手动模拟

仍以:

复制代码
nums1 = [1, 4, 7, 10]
nums2 = [2, 3, 5, 6, 8, 9]
half = 5

为例:

复制代码
left = -1, right = 4
i = 1, j = 4
nums1[i] = 4 < nums2[j-1] = 6
说明 nums1 左边取少了:left = 1

下一轮:

复制代码
left = 1, right = 4
i = 2, j = 3
nums1[i] = 7 >= nums2[j-1] = 5
说明 i 可能已经足够:right = 2

此时 left + 1 == right,二分结束,最终分割为:

复制代码
nums1: [1, 4]    | [7, 10]
nums2: [2, 3, 5] | [6, 8, 9]

左半边最大值为 max(4,5)=5,右半边最小值为 min(7,6)=6,所以中位数为:

复制代码
(5 + 6) / 2 = 5.5

代码实现

复制代码
class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        if (nums1.length > nums2.length) {
            int[] temp = nums1;
            nums1 = nums2;
            nums2 = temp;
        }

        int m = nums1.length;
        int n = nums2.length;
        int half = (m + n + 1) / 2;

        int left = -1;
        int right = m;

        while (left + 1 < right) {
            int i = left + (right - left) / 2;
            int j = half - i;

            if (nums1[i] < nums2[j - 1]) {
                left = i;
            } else {
                right = i;
            }
        }

        int i = right;
        int j = half - i;

        int left1 = i == 0 ? Integer.MIN_VALUE : nums1[i - 1];
        int right1 = i == m ? Integer.MAX_VALUE : nums1[i];
        int left2 = j == 0 ? Integer.MIN_VALUE : nums2[j - 1];
        int right2 = j == n ? Integer.MAX_VALUE : nums2[j];

        int maxLeft = Math.max(left1, left2);

        if ((m + n) % 2 == 1) {
            return maxLeft;
        }

        int minRight = Math.min(right1, right2);
        return ((double) maxLeft + minRight) / 2.0;
    }
}

复杂度分析

只在较短数组上进行二分,搜索范围为 m,所以时间复杂度为:

复制代码
O(log(min(m,n)))

它满足题目要求的 O(log(m+n))。算法只使用常量级变量,空间复杂度为:

复制代码
O(1)

常见错误

  1. 奇数长度时,中位数应为左半边最大值 max(left1,left2),不能误用右半边的 right1
  2. right1 必须来自 nums1[i]left2 必须来自 nums2[j-1]
  3. 计算偶数长度中位数时,先转为 double,避免两个 int 相加溢出。

总结

这道题的关键不是合并数组,而是寻找一个分割,使得:

复制代码
i + j = half
left1 <= right2
left2 <= right1

只要左半边和右半边的数量、顺序都正确,就可以直接通过四个边界值得到中位数。

相关推荐
Kisorge1 小时前
【电机控制器】 基于STSPIN32G4的FOC控制
stm32·嵌入式硬件·算法
Elivs1 小时前
RMSNorm函数
人工智能·算法·机器学习
敲上瘾2 小时前
redis常用数据类型与操作方法
数据结构·数据库·redis·缓存
tudousisi2222 小时前
P4447 [AHOI2018初中组] 分组 题解复盘
算法
SNAKEpc121382 小时前
OpenGL(十一)- 变换管线
c语言·c++·算法·矩阵·图形渲染
牢姐与蒯2 小时前
c++高级数据结构之图(基本概念,存储结构,BFS,DFS,最小生成树)
数据结构·c++·
Tongzhi20262 小时前
考勤数据本地化存储:通芝科技无感考勤一体机的数据安全逻辑
大数据·数据结构·科技·均值算法·数据库开发·推荐算法
她说可以呀3 小时前
Spring-ai-Rag-多文件格式生成 List<Document>
数据结构·list
饼饼学习空间智能4 小时前
遥操作数据能否提升机器人自主化水平?详解模仿学习、仿真放大与Sim2Real闭环
人工智能·算法