【问题标题】:Median of 5 sorted arrays5 个排序数组的中位数
【发布时间】:2011-09-05 03:58:17
【问题描述】:

我正在尝试找到 5 个排序数组的中位数的解决方案。这是一个面试题。

我能想到的解决方案是合并 5 个数组,然后找到中位数 [O(l+m+n+o+p)]。

我知道对于 2 个相同大小的排序数组,我们可以在 log(2n) 中完成。 [通过比较两个数组的中值,然后丢弃每个数组的一半并重复该过程]。 ..查找中位数可以是排序数组中的常数时间..所以我认为这不是 log(n) 吗? .. 这个时间复杂度是多少?

1] 5 个数组是否有类似的解决方案。如果数组大小相同怎么办,那么有更好的解决方案吗?

2] 我假设既然这被要求为 5,那么对于 N 个排序数组会有一些解决方案吗?

感谢您的任何指点。

我向面试官问了一些澄清/问题:
数组的长度是否相同
=> 没有
我想数组的值会有重叠
=> 是的

作为练习,我认为 2 个数组的逻辑没有扩展。这是一个尝试:
将上述 2 个数组的逻辑应用于 3 个数组: [3,7,9] [4,8,15] [2,3,9] ... 中位数 7,8,3
抛出元素 [3,7,9] [4,8] [3,9] .. 中位数 7,6,6
抛出元素 [3,7] [8] [9] ..medians 5,8,9 ...
抛出元素 [7] [8] [9] .. 中位数 = 8 ... 这似乎不正确?

已排序元素的合并 => [2,3,4,7,8,9,15] => 预期中位数 = 7

【问题讨论】:

  • 它们是单独排序的,还是每个数组也代表一个范围,在这个范围内另一个数组没有值?即,如果一个人的值在 1-5 范围内,那么另一个人的值可以在同一范围内吗?如果不是,那么您只需要确定数组的顺序(从最低到最高范围),将它们的所有长度相加,中间元素除以 2,然后转到包含该元素的数组。
  • 感谢 filip-fku。我解决了你的问题。
  • 这是一个臭名昭著的问题,因为这个想法相对容易,但要正确实施却极其困难。对于 k > 2,实现变得更糟。对我来说,这不适合技术面试。

标签: arrays algorithm logic


【解决方案1】:

(这是您对两个数组的想法的概括。)

如果从五个数组的五个中位数开始,显然整体中位数必须介于五个中位数的最小值和最大值之间。

证明是这样的:如果 a 是中位数的最小值,b 是中位数的最大值,那么每个数组有不到一半的元素小于 a,不到一半的元素大于 b .结果如下。

所以在包含a的数组中,丢弃小于a的数字;在包含 b 的数组中,丢弃大于 b 的数字...但只从两个数组中丢弃相同数量的元素。

也就是说,如果 a 是其数组开头的 j 个元素,b 是其数组末尾的 k 个元素,则丢弃 a 数组中的第一个 min(j,k) 个元素和最后一个 min( j,k) b 数组中的元素。

迭代直到总共减少 1 或 2 个元素。

这些操作中的每一个(即,找到已排序数组的中位数并从数组的开头或结尾丢弃 k 个元素)都是常数时间。所以每次迭代都是常数时间。

每次迭代都会从至少一个数组中丢弃(超过)一半的元素,并且您只能对五个数组中的每一个执行 log(n) 次...所以整体算法是 log(n)。

[更新]

正如 Himadri Choudhury 在 cmets 中指出的那样,我的解决方案是不完整的;有很多细节和极端情况需要担心。所以,稍微充实一下……

对于五个数组 R 中的每一个,将其“下中位数”定义为 R[n/2-1],将其“上中位数”定义为 R[n/2],其中 n 是数组中的元素数(并且数组从 0 开始索引,并向下除以 2)。

设“a”为下中位数中的最小值,“b”为上中位数中的最大值。如果有多个具有最小下中位数的数组和/或多个具有最大上中位数的数组,请从不同的数组中选择 a 和 b(这是其中一种极端情况)。

现在,借用 Himadri 的建议:从其数组中删除所有元素直到 并包括 a,并从其数组中删除所有元素直到 并包括 b,注意从两个数组中删除相同数量的元素。注意 a 和 b 可以在同一个数组中;但如果是这样,它们就不能具有相同的值,因为否则我们将能够从不同的数组中选择其中一个。因此,如果这一步最终会丢弃同一数组的开头和结尾的元素。

只要您有三个或更多数组,就进行迭代。但是一旦你只剩下一两个数组,你就必须改变你的策略,让它变得独占而不是包容;您最多只能擦除 但不包括 a 和向下 但不包括 b。继续这样,只要剩下的一两个数组都至少有三个元素(保证你取得进步)。

最后,您将减少几种情况,其中最棘手的是剩下两个数组,其中一个有一个或两个元素。现在,如果我问你:“给定一个排序数组加上一两个附加元素,找到所有元素的中位数”,我认为你可以在恒定时间内做到这一点。 (同样,有很多细节需要敲定,但基本思想是向数组添加一两个元素并不会“推动中位数”非常多。)

【讨论】:

  • 看起来通过这种方法我们最终减少了第一个和最后一个数组。你能用一个例子解释一下吗,这是我对 3 个数组的尝试... [3,7,9] [4,8,15] [2,3,9] ... 中位数 7,8,3 .. 抛出元素 [3,7,9] [4,8] [3,9] .. 中位数 7,6,6 .. 抛出元素 [3,7] [8] [9] ..中位数 5,8,9 。 .. 抛出元素 [7] [8] [9] .. 中位数 = 8 ... 这似乎不正确?
  • 你确实减少了第一个和最后一个数组,但是为了运行时间证明你只需要知道你扔掉了一个数组的至少一半。我不知道是否有可能击败 O(log n)。顺便说一句,很好的面试问题。
  • 应该修改解决方案,以便将数字 >= 扔到最大中位数, 或
  • @mgkrebbs。是的。 Nemo 的想法是你丢弃相同数量的元素 >= max 和 [3,7,9] [4,8] [3,9 ] -> [3,7] [8] [3,9] -> [7] [] [3,9] -> 7 (这个解决方案在实践中有点棘手,有很多极端情况:奇数/偶数#个元素,单元素数组等,但这是大体思路)。
  • 我想指出下中位数的计算是不正确的。例如,如果一个数组只有 1 个元素,则下中位数为 (1 / 2) -1 = -1,这是一个无效索引。我认为 (N - 1) /2 是正确的方法。
【解决方案2】:

应该很直接地将相同的想法应用于 5 个数组。

首先,将问题转换为更一般的问题。在 N 个有序数组中查找第 K 个元素

  1. 使用二分查找在每个已排序数组中查找第 (K/N) 个元素,例如 K1、K2...KN

  2. Kmin = min(K1 ... KN), Kmax = max(K1 ... KN)

  3. 丢弃所有小于 Kmin 或大于 Kmax 的元素,比如 X 元素已被丢弃。

  4. 现在通过在已排序的数组中查找第 (K - X) 个元素以及剩余元素来重复该过程

【讨论】:

  • 二进制搜索本身就是 log(n),而你正在做 log(n) 次,所以这是 O((log(n))^2)。但是你可以在 O(log n) 中解决这个问题 :-)
  • Aach 数组已排序,因此您不需要二进制搜索来找到它们的第 (N/K) 个最小元素。他们只是 A1[N/K], A2[N/K], ....
  • 我看不出 Kmax 在 1.&2. 中有什么用途。你可能需要 如果 0 .
  • 顺便问一下,K 和 X 是什么?
【解决方案3】:

您不需要对 5 个数组进行完全合并。你可以进行归并排序,直到你有 (l+n+o+p+q)/2 个元素,然后你就有了中值。

【讨论】:

  • 您甚至不需要进行排序——只需进行比较即可。只需将 5 个排序数组的所有成员中途保存即可。
【解决方案4】:

可以通过binary search在排序列表中查找第k个元素。

from bisect import bisect_left
from bisect import bisect_right

def kthOfPiles(givenPiles, k, count):
    '''
    Perform binary search for kth element in  multiple sorted list

    parameters
    ==========
    givenPiles  are list of sorted list
    count   is the total number of
    k       is the target index in range [0..count-1]
    '''
    begins = [0 for pile in givenPiles]
    ends = [len(pile) for pile in givenPiles]
    #print('finding k=', k, 'count=', count)
    
    for pileidx,pivotpile in enumerate(givenPiles):
        
        while begins[pileidx] < ends[pileidx]:
            mid = (begins[pileidx]+ends[pileidx])>>1
            midval = pivotpile[mid]
            
            smaller_count = 0
            smaller_right_count = 0
            for pile in givenPiles:
                smaller_count += bisect_left(pile,midval)
                smaller_right_count += bisect_right(pile,midval)
                
            #print('check midval', midval,smaller_count,k,smaller_right_count)
            if smaller_count <= k and k < smaller_right_count:
                return midval
            elif smaller_count > k:
                ends[pileidx] = mid
            else:
                begins[pileidx] = mid+1
            
    return -1

def medianOfPiles(givenPiles,count=None):
    '''
    Find statistical median
    Parameters:
    givenPiles  are list of sorted list
    '''
    if not givenPiles:
        return -1 # cannot find median
    
    if count is None:
        count = 0
        for pile in givenPiles:
            count += len(pile)
            
    # get mid floor
    target_mid = count >> 1
    midval = kthOfPiles(givenPiles, target_mid, count)
    if 0 == (count&1):
        midval += kthOfPiles(givenPiles, target_mid-1, count)
        midval /= 2
        
    return '%.1f' % round(midval,1)

上面的代码也给出了正确的统计中位数。

将上面的二分搜索与patience-sort 结合起来,提供了一种有价值的技术。

还有一个值得一提的median of median 算法用于选择枢轴。它给出了近似值。我想这与我们在这里要求的不同。

【讨论】:

    【解决方案5】:

    使用 heapq 保留每个列表的最小候选者。

    前提:N个排序的K长列表

    O(NKlgN)

    import heapq
    class Solution:
        def f1(self, AS):
            def f(A): 
                n = len(A)
                m = n // 2
                if n % 2:
                    return A[m]
                else:
                    return (A[m - 1] + A[m]) / 2
            res = []
            q = []
            for i, A in enumerate(AS):
                q.append([A[0], i, 0])
            heapq.heapify(q)
            N, K = len(AS), len(AS[0])
            while len(res) < N * K:
                mn, i, ii = heapq.heappop(q)
                res.append(mn)
                if ii < K - 1:
                    heapq.heappush(q, [AS[i][ii + 1], i, ii + 1])
            return f(res)
    
        def f2(self, AS):
            q = []
            for i, A in enumerate(AS):
                q.append([A[0], i, 0])
            heapq.heapify(q)
            N, K = len(AS), len(AS[0])
            n = N * K
            m = n // 2
            m1 = m2 = float('-inf')
            k = 0
            while k < N * K:
                mn, i, ii = heapq.heappop(q)
                res.append(mn)
                k += 1
                if k == m - 1:
                    m1 = mn
                elif k == m:
                    m2 = mn
                    return m2 if n % 2 else (m1 + m2) / 2 
                if ii < K - 1:
                    heapq.heappush(q, [AS[i][ii + 1], i, ii + 1])
            return 'should not go here'      
    

    【讨论】:

      猜你喜欢
      • 2013-09-18
      • 1970-01-01
      • 1970-01-01
      • 2017-08-28
      • 2020-11-18
      • 1970-01-01
      • 2014-10-25
      • 1970-01-01
      相关资源
      最近更新 更多