具有排序行的矩阵的中位数

时间:2017-01-01 09:10:16

标签: algorithm matrix data-structures binary-search

我无法以最佳方式解决以下问题,也无法找到在任何地方执行此操作的方法。

  

给定N×M矩阵,其中每行被排序,找到矩阵的整体中值。假设N * M是奇数。

     

例如,

     

矩阵=
  [1,3,5]
  [2,6,9]   [3,6,9]

     

A = [1,2,3,3,5,6,6,9,9]

     

中位数是5.所以,我们返回5.
  注意:不允许额外的内存。

任何帮助将不胜感激。

8 个答案:

答案 0 :(得分:11)

考虑以下过程。

  • 如果我们将N * M矩阵视为1-D数组,则中位数是1+N*M/2元素的元素。

  • 如果x是矩阵的元素,矩阵元素的数量≤x等于1 + N*M/2,则认为x将是中位数。

  • 由于每行中的矩阵元素都已排序,因此您可以轻松找到每行less than or equals x中的元素数量。为了在整个矩阵中找到,复杂度为N*log M,使用二分搜索。

  • 然后首先从N * M矩阵中找到最小和最大元素。在该范围上应用二进制搜索,并为每个x运行上述功能。

  • 如果矩阵≤ x中的元素数量为1 + N*M/2且x包含在该矩阵中,则x为中位数。

您可以在C ++代码下面考虑这个:

int median(vector<vector<int> > &A) {
    int min = A[0][0], max = A[0][0];
    int n = A.size(), m = A[0].size();
    for (int i = 0; i < n; ++i) {
        if (A[i][0] < min) min = A[i][0];
        if (A[i][m-1] > max) max = A[i][m-1];
    }

    int element = (n * m + 1) / 2;
    while (min < max) {
        int mid = min + (max - min) / 2;
        int cnt = 0;
        for (int i = 0; i < n; ++i)
            cnt += upper_bound(&A[i][0], &A[i][m], mid) - &A[i][0];
        if (cnt < element)
            min = mid + 1;
        else
            max = mid;
    }
    return min;
}

答案 1 :(得分:4)

这个问题与在行和列的排序矩阵中找到第k个最小元素非常相似。

因此,可以使用二进制搜索和优化的排序矩阵中的计数来解决此问题。二进制搜索需要O(log(n))时间,对于每个搜索值,平均需要进行n次迭代才能找到小于搜索到的数字的数字。用于二进制搜索的搜索空间被限制为矩阵中mat [0] [0]的最小值和最大值mat [n-1] [n-1]。

对于从二分搜索中选择的每个数字,我们需要计算小于或等于该特定数字的数字。这样就可以找到第k ^个最小数字或中位数。

为更好地理解您可以参考以下视频:

https://www.youtube.com/watch?v=G5wLN4UweAM&t=145s

答案 2 :(得分:1)

一个简单的O(1)内存解决方案是检查每个单独的元素 z 是否为中位数。为此,我们在所有行中找到 z 的位置,只是累积小于 z 的元素数。除了在 O(log M)时间的每一行中查找 z 的位置之外,这不会使用每行进行排序的事实。对于每个元素,我们需要进行 N * log M 比较,并且有 N * M 元素,因此它是N²MlogM 。< / p>

答案 3 :(得分:1)

如果矩阵元素是整数,则可以从hi和low的矩阵范围开始二进制搜索中值。 O(n log m log(hi-low))。

否则,具有O(n²log²m)wost-case时间复杂度的一种方法是二进制搜索,O(log m),依次为每一行,O(n),最接近整个矩阵中值的元素。左边和最右边的O(n log m),到目前为止更新最好。我们知道整体中位数不超过floor(m * n / 2)元素,严格小于它,并且添加小于它的元素数量及其出现次数可以不小于floor(m * n / 2) + 1。我们在行上使用标准二进制搜索,并且 - 正如greybeard指出的那样 - 跳过我们“最佳”范围之外的元素的测试。对元素与总体中位数的接近程度的测试包括计算每行中有多少元素严格小于它以及有多少元素相等,这是O(n log m)时间n二进制搜索所实现的。由于行是排序的,我们知道更大的元素将更“向右”,而较小的元素则更多地“向左”与整体中位数相关。

如果允许重新排列矩阵,则可以通过对矩阵进行排序(例如,使用块排序)并返回中间元素来实现O(mn log(mn))时间复杂度。

答案 4 :(得分:1)

我编写了גלעדברקן的O(n 2 log 2 m)时间解决方案,但他们要求我不要将代码添加到他们的答案中,所以这是一个单独的答案:

import bisect

def MedianDistance(key, matrix):
  lo = hi = 0
  for row in matrix:
    lo += bisect.bisect_left(row, key)
    hi += bisect.bisect_right(row, key)
  mid = len(matrix) * len(matrix[0]) // 2;
  if hi - 1 < mid: return hi - 1 - mid
  if lo > mid: return lo - mid
  return 0

def ZeroInSorted(row, measure):
  lo, hi = -1, len(row)
  while hi - lo > 1:
    mid = (lo + hi) // 2
    ans = measure(row[mid])
    if ans < 0: lo = mid
    elif ans == 0: return mid
    else: hi = mid

def MatrixMedian(matrix):
  measure = lambda x: MedianDistance(x, matrix)
  for idx, row in enumerate(matrix):
    if not idx & idx-1: print(idx)
    ans = ZeroInSorted(row, measure)
    if ans is not None: return row[ans]

答案 5 :(得分:0)

带有改进和python代码的

sunkuet02's answer
N×M矩阵A的每一行都被排序并具有中间元素,即中位数 至少N *(M + 1)/ 2个元素不大于这些中值的最大 hi ,并且至少N *(M + 1)/ 2不小于最小值 LO
A的所有元素的中位数必须在 lo hi 之间,包括在内。
一旦已知超过一半的元素低于当前候选元素,则已知后者高。一旦剩余的行数太少,低于当前候选者的元素数量达到总数的一半,则候选人知道为低:在两种情况下,立即进入下一个候选人。

from bisect import bisect

def median(A):
    """ returns the median of all elements in A.
        Each row of A needs to be in ascending order. """
    # overall median is between min and max row median
    lo, hi = minimax(A)
    n = len(A)
    middle_row = n // 2
    columns = len(A[0])
    half = (n * columns + 1) // 2
    while lo < hi:
        mid = lo + (hi - lo) // 2
        lower = 0
        # first half can't decide median
        for a in A[:middle_row]:
            lower += bisect(a, mid)
        # break as soon as mid is known to be too high or low
        for r, a in enumerate(A[middle_row:n-1]):
            lower += bisect(a, mid)
            if half <= lower:
                hi = mid
                break
            if lower < r*columns:
                lo = mid + 1
                break
        else: # decision in last row
            lower += bisect(A[n-1], mid)
            if half <= lower:
                hi = mid
            else:
                lo = mid + 1

    return lo


def minmax(x, y):
    """return min(x, y), max(x, y)"""
    if x < y:
        return x, y
    return y, x


def minimax(A):
    """ return min(A[0..m][n//2]), max(A[0..m][n//2]):
        minimum and maximum of medians if A is a
        row major matrix with sorted rows."""
    n = len(A)
    half = n // 2
    if n % 2:
        lo = hi = A[0][half]
    else:
        lo, hi = minmax(A[0][half], A[1][half])
    for i in range(2-n % 2, len(A[0]), 2):
        l, h = minmax(A[i][half], A[i+1][half])
        if l < lo:
            lo = l
        if hi< h:
            hi = h
    return lo, hi


if __name__ =='__main__':
    print(median( [[1, 3, 5], [2, 6, 9], [3, 6, 9]] ))

(我认为std::upper_bound()bisect.bisect()是等效的(bisect_right()是别名)。)
对于第二个候选中值,处理的最后一行可能低于第一次迭代中的行。在接下来的迭代中,rownumber应该永远不会减少 - 懒得将中的分解为((重命名并且)在适当时增加middle_row

答案 6 :(得分:0)

使用拉斯维加斯算法:

from random import randint

def findMedian(matrix):
    #getting the length of columns and rows
     N = len(matrix)
     M = len(matrix[0])
     while True:
           counter = 0
           #select a row randomly
           u = randint(0,len(matrix)-1)
           #select a column randomly
           v = randint(0,len(matrix[0])-1)
           #random index
           x = matrix[u][v]
          for i in range(len(matrix)):
             for j in range(len(matrix[0])):
                 if matrix[i][j] < x:
                        counter+=1
          #finding median
          if counter == (N*M-1)//2:
     return (x)



 arr = [[1,3,5],
        [2,6,9],
        [3,6,9]]

 findMedian(arr)  

答案 7 :(得分:0)

正如我所见,人们对@ sunkuet02算法有很多疑问。我将尝试回答这个问题。这可能对其他人有帮助。

提问@ user248884。

1)为什么它是max = mid而不是max = mid-1?

 if (cnt < element)
        min = mid + 1;
    else
        max = mid;

解决方案可能是中间因素。假设我们有r = 1 c = 4和A [] [4] = {{1,2,9,10}}; 此处,最小值= 1,最大值= 10。因此,mid = 5就是答案。 这意味着您不能离开中间元素。

2)为什么我们返回分钟? 其实没关系。您也可以返回max,因为两者都会给出相同的答案。

while (min < max) { ...}

当min == max时,循环将中断。因此,可以返回最小值或最大值并不重要。想一想。

@Seaky Lone提出的问题

3。什么是upper_bound()? 此函数需要3个参数数组开始(更确切地说是迭代器开始),最后一个索引直到您要检查(不包括最后一个元素)和一个 X 。它将返回大于 X 的第一个元素索引。通过使用此函数可以获得少于X的元素数。

@ dk123提出的问题

4。我们如何确保中位数实际上是矩阵内的数字?

在这种算法中,我们不检查数组中是否存在中位数。 为此,在获得可能的中位数后,检查矩阵是否存在。如果不是,则找到最近的最小元素为中位数