1. 概述

在本教程中,我们将讨论在一个每行有序的矩阵中查找中位数的问题。

首先我们会定义这个问题,并通过一个例子来说明。接着,我们会介绍两种解决方法,并给出它们的实现方式和时间复杂度分析。


2. 问题定义

假设我们有一个矩阵 M,它有 R 行且每行都已排序,每行有 C 列。我们的目标是找出该矩阵的中位数。⚠️ 注意:中位数是将所有元素排序后位于中间的那个元素

举个例子,考虑如下这个每行有序的矩阵:

Matrix 1

将所有元素展开排序后为 [1, 2, 4, 5, 7, 9],总共有 6 个元素,中位数位于第 floor(6/2) = 5 个位置(从 0 开始),所以中位数是 5


3. 简单暴力法(Naive Approach)

3.1 核心思想

✅ 将所有元素放入一个列表中,然后排序,最后取中间元素。

虽然这种方法思路简单,但效率不高,特别是当矩阵很大时,排序的开销会非常大。

3.2 实现代码

double medianNaive(int[][] M) {
    int R = M.length;
    int C = M[0].length;
    List<Integer> list = new ArrayList<>();

    for (int i = 0; i < R; i++) {
        for (int j = 0; j < C; j++) {
            list.add(M[i][j]);
        }
    }

    Collections.sort(list);
    int medianPos = (R * C) / 2;
    return list.get(medianPos);
}

3.3 复杂度分析

  • 时间复杂度O(R * C * log(R * C)),主要是排序的开销。
  • 空间复杂度O(R * C),需要额外存储所有元素。

4. 二分查找法(Binary Search)

4.1 核心思想

✅ 利用每行有序的特性,在数值范围内进行二分查找,判断当前中间值是否可能是中位数。

我们二分查找“可能的中位数值”,每一步都统计有多少个数小于等于当前中间值 mid,如果这个数量大于等于中位数的位置(即 (R * C) / 2),说明中位数可能在左边或就是 mid,否则说明中位数在右边。

4.2 辅助函数:统计小于等于 X 的元素个数

int countSmallerOrEqual(int[][] M, int X) {
    int R = M.length;
    int C = M[0].length;
    int count = 0;

    for (int i = 0; i < R; i++) {
        int low = 0;
        int high = C - 1;
        int pos = C;

        while (low <= high) {
            int mid = low + (high - low) / 2;
            if (M[i][mid] > X) {
                pos = mid;
                high = mid - 1;
            } else {
                low = mid + 1;
            }
        }
        count += pos;
    }

    return count;
}

⚠️ 这个函数利用每行有序的特性,使用二分查找快速定位每行中小于等于 X 的元素个数。

4.3 主算法实现

int medianBinarySearch(int[][] M) {
    int R = M.length;
    int C = M[0].length;

    int low = Integer.MIN_VALUE;
    int high = Integer.MAX_VALUE;
    int answer = 0;

    while (low <= high) {
        int mid = low + (high - low) / 2;
        int count = countSmallerOrEqual(M, mid);

        if (count >= (R * C + 1) / 2) {
            answer = mid;
            high = mid - 1;
        } else {
            low = mid + 1;
        }
    }

    return answer;
}

⚠️ 注意这里 (R * C + 1) / 2 是为了兼容奇数和偶数长度的中位数逻辑(取中间偏左的那个)。

4.4 复杂度分析

  • 时间复杂度O(32 * R * log C)
    • 32 是整数范围的二进制位数(相当于最多做 32 次二分查找)
    • 每次二分查找对每行执行一次二分统计(O(log C)
  • 空间复杂度O(1),仅使用常量级额外空间

5. 总结

我们介绍了两种查找矩阵中位数的方法:

方法 时间复杂度 空间复杂度 适用场景
暴力法 O(RC log RC) O(RC) 数据量小
二分法 O(32 × R × log C) O(1) 数据量大、内存敏感

推荐使用二分法,尤其在数据量大、内存有限的场景下,效率更高且空间占用小。

⚠️ 踩坑提醒:

  • 使用 floor((R * C) / 2) 时注意奇偶性处理
  • 二分查找边界要设置合理(Integer.MIN_VALUEInteger.MAX_VALUE
  • countSmallerOrEqual 函数的逻辑要清晰,否则容易漏统计或错统计

如需进一步优化,可结合堆结构或归并排序思路,但上述二分法在多数情况下已足够高效。


原始标题:Median of a Matrix With Sorted Rows