📌  相关文章
📜  在按行排序的矩阵中查找中位数(1)

📅  最后修改于: 2023-12-03 15:37:42.547000             🧑  作者: Mango

在按行排序的矩阵中查找中位数

在一个$m\times n$的矩阵中,每行都按照升序排序,求该矩阵的中位数。如果有多个中位数,则返回左边的中位数。

思路

最朴素的方法当然是将全部元素复制到一个数组中,然后直接求解中位数。这个方法的时间复杂度是$O(mn)$,空间复杂度也是$O(mn)$,显然是不能接受的。

我们可以观察到每行都是升序排序的,那么对于中位数一定有以下性质:

  • 所有小于中位数的数一定在矩阵的左上部分,即矩阵的左上角子矩阵中;
  • 所有大于中位数的数一定在矩阵的右下部分,即矩阵的右下角子矩阵中;
  • 中位数可能在左上部分或右下部分中,需要通过二分查找来确定。

因此,我们可以用类似于二分查找的思路,在矩阵的左上角和右下角不断缩小子矩阵的范围,最终找到中位数。具体来说,我们可以假设左上角为$(i,j)$,右下角为$(p,q)$,那么中位数所在位置应该为$(i+p)/2$行$[j,q]$列的位置,我们可以通过不断缩小矩阵的上下边界$i$和$p$,最后得到中位数。

代码

以下是完整的C++代码实现(时间复杂度$O(m\log n)$):

class Solution {
public:
    int median(vector<vector<int>>& matrix) {
        int m = matrix.size(), n = matrix[0].size();
        int left = matrix[0][0], right = matrix[m-1][n-1];
        while (left <= right) {
            int mid = left + (right - left) / 2;
            int cnt = 0, j = n - 1;
            for (int i = 0; i < m; ++i) {
                while (j >= 0 && matrix[i][j] > mid) --j;
                cnt += (j + 1);
            }
            if (cnt < (m * n + 1) / 2)
                left = mid + 1;
            else
                right = mid - 1;
        }
        return left;
    }
};

其中算法的核心部分是二分查找。我们首先得到矩阵中的最小值和最大值,然后不断用二分法缩小最小值和最大值之间的范围。对于每个二分的mid,我们统计整个矩阵中有多少个数小于它,如果小于一半,那么中位数必然大于mid,否则中位数必然小于mid。

具体来说,对于每一行,我们不断从右往左找到第一个小于等于mid的数的位置,就可以算出当前行小于等于mid的数字的数量。把每一行的数量加在一起,就是整个矩阵中小于等于mid的数字的数量。

这个算法的时间复杂度是$O(m\log n)$,其中$m$是矩阵的行数,$n$是矩阵的列数。