45

I am trying to find the solution for median of 5 sorted arrays. This was an interview questions.

The solution I could think of was merge the 5 arrays and then find the median [O(l+m+n+o+p)].

I know that for 2 sorted arrays of same size we can do it in log(2n). [by comparing the median of both arrays and then throwing out 1 half of each array and repeating the process]. .. Finding median can be constant time in sorted arrays .. so I think this is not log(n) ? .. what is the time complexity for this ?

1] Is there a similar solution for 5 arrays . What if the arrays are of same size , is there a better solution then ?

2] I assume since this was asked for 5, there would be some solution for N sorted arrays ?

Thanks for any pointers.

Some clarification/questions I asked back to the interviewer:
Are the arrays of same length
=> No
I guess there would be an overlap in the values of arrays
=> Yes

As an exercise, I think the logic for 2 arrays doesnt extend . Here is a try:
Applying the above logic of 2 arrays to say 3 arrays: [3,7,9] [4,8,15] [2,3,9] ... medians 7,8,3
throw elements [3,7,9] [4,8] [3,9] .. medians 7,6,6
throw elements [3,7] [8] [9] ..medians 5,8,9 ...
throw elements [7] [8] [9] .. median = 8 ... This doesnt seem to be correct ?

The merge of sorted elements => [2,3,4,7,8,9,15] => expected median = 7

codeObserver
  • 6,521
  • 16
  • 76
  • 121
  • 1
    Are they each individually sorted, or does each array also represent a range within which there is no value from another of the arrays? i.e. if one has values in the range 1-5, could another have values in the same range? If not, then you just need to determine the ordering of the arrays (lowest to highest range), sum all their lengths, divide by 2 for the middle element and go to the array that has that element. – filip-fku May 31 '11 at 03:03
  • Thanks filip-fku. I addressed your questions. – codeObserver May 31 '11 at 03:10
  • 3
    It's a notorious problem because the idea is relatively easy but it's extremely hard to implement correctly. For k > 2, the implementation gets worse. To me, this is not a good one for tech interviews. – galactica Jul 12 '13 at 03:11

5 Answers5

29

(This is a generalization of your idea for two arrays.)

If you start by looking at the five medians of the five arrays, obviously the overall median must be between the smallest and the largest of the five medians.

Proof goes something like this: If a is the min of the medians, and b is the max of the medians, then each array has less than half of its elements less than a and less than half of its elements greater than b. Result follows.

So in the array containing a, throw away numbers less than a; in the array containing b, throw away numbers greater than b... But only throw away the same number of elements from both arrays.

That is, if a is j elements from the start of its array, and b is k elements from the end of its array, you throw away the first min(j,k) elements from a's array and the last min(j,k) elements from b's array.

Iterate until you are down to 1 or 2 elements total.

Each of these operations (i.e., finding median of a sorted array and throwing away k elements from the start or end of an array) is constant time. So each iteration is constant time.

Each iteration throws away (more than) half the elements from at least one array, and you can only do that log(n) times for each of the five arrays... So the overall algorithm is log(n).

[Update]

As Himadri Choudhury points out in the comments, my solution is incomplete; there are a lot of details and corner cases to worry about. So, to flesh things out a bit...

For each of the five arrays R, define its "lower median" as R[n/2-1] and its "upper median" as R[n/2], where n is the number of elements in the array (and arrays are indexed from 0, and division by 2 rounds down).

Let "a" be the smallest of the lower medians, and "b" be the largest of the upper medians. If there are multiple arrays with the smallest lower median and/or multiple arrays with the largest upper median, choose a and b from different arrays (this is one of those corner cases).

Now, borrowing Himadri's suggestion: Erase all elements up to and including a from its array, and all elements down to and including b from its array, taking care to remove the same number of elements from both arrays. Note that a and b could be in the same array; but if so, they could not have the same value, because otherwise we would have been able to choose one of them from a different array. So it is OK if this step winds up throwing away elements from the start and end of the same array.

Iterate as long as you have three or more arrays. But once you are down to just one or two arrays, you have to change your strategy to be exclusive instead of inclusive; you only erase up to but not including a and down to but not including b. Continue like this as long as both of the remaining one or two arrays has at least three elements (guaranteeing you make progress).

Finally, you will reduce to a few cases, the trickiest of which is two arrays remaining, one of which has one or two elements. Now, if I asked you: "Given a sorted array plus one or two additional elements, find the median of all elements", I think you can do that in constant time. (Again, there are a bunch of details to hammer out, but the basic idea is that adding one or two elements to an array does not "push the median around" very much.)

Timothy Swan
  • 623
  • 3
  • 9
  • 21
Nemo
  • 70,042
  • 10
  • 116
  • 153
  • It looks like by this approach we end up reducing the first and the last array. Can you explain with an example, here is my try for 3 arrays... [3,7,9] [4,8,15] [2,3,9] ... medians 7,8,3 .. throw elements [3,7,9] [4,8] [3,9] .. medians 7,6,6 .. throw elements [3,7] [8] [9] ..medians 5,8,9 ... throw elements [7] [8] [9] .. median = 8 ... This doesnt seem to be correct ? – codeObserver May 31 '11 at 03:17
  • You do reduce both the first and last array, but for the running time proof you only need to know that you throw away at least half of one array. I do not know whether it is possible to beat O(log n). Nice interview question, by the way. – Nemo May 31 '11 at 03:19
  • 3
    The solution should be modified so that you throw away numbers >= to the max median and <= to the smallest median ( instead of strict > or < ). Otherwise, say an array has only 1 element, then you won't be able to throw anything away, and the whole process will hang right there. Otherwise, this is a nice solution. – Himadri Choudhury May 31 '11 at 04:10
  • @p1. You are neglecting the rule that a balanced number of elements must be thrown away at each turn in your example. In this step [3,7,9] [4,8] [3,9] -> [3,7] [8] [9] you throw away '9' on the high side and then '4' and '3' on the low side. You can only throw away either '3' or '4'. – Himadri Choudhury May 31 '11 at 04:12
  • Thnx Himadri .. If I throw away (<= and >=) .. here is the changed example:[3,7,9] [4,8,15] [2,3,9] ... medians 7,8,3 throw elements [3,7,9] [4] [9] .. medians 7,4,9 throw elements [3,7] ..medians 5..... This doesnt seem to be correct ? ... Can you please solve this example and paste .. I am probably missing something ? – codeObserver May 31 '11 at 04:23
  • @p1: It looks like the second "throw" was wrong in that it modified an array other than the max- or min- array. So, instead of [3,7], the next cycle should have had [3,7,9] to work with, leading to 7, the right median for that data. – mgkrebbs May 31 '11 at 05:07
  • 1
    @ mgkrebbs. Yeah. Nemo's idea is that you throw away the same number of elements >= max and <= min since those can't possibly have been the median. So, in your example this would be the sequence: [3,7,9] [4,8,15] [2,3,9] -> [3,7,9] [4,8] [3,9] -> [3,7] [8] [3,9] -> [7] [] [3,9] -> 7 ( this solution is a bit tricky to get right in practice there are a bunch of corner cases: odd/even # elements, single element arrays, etc. but that's the general idea ). – Himadri Choudhury May 31 '11 at 05:58
  • thnx mgkrebbs..here is the changed example again:[3,7,9] [4,8,15] [2,3,9] ... medians 7,8,3 after throwing elements [3,7,9] [4] [9] .. medians 7,4,9 after throwing elements [3,7,9] ..medians = 7 – codeObserver Jun 01 '11 at 04:57
  • @Himadri: Some questions to your example: [3,7,9] [4,8,15] [2,3,9] -> [3,7,9] [4,8] [3,9] [Here you are not throwing <= and >= ..but only < and > contradicting your own suggestion ?]-> [3,7] [8] [3,9] -> [7] [] [3,9] [Here the medians are 5,8,6 .. so the resultant array should be (7)(8)(9) ?]-> 7 – codeObserver Jun 01 '11 at 04:58
  • @Nemo did you actually mean to say "If a is the min of the medians, and b is the max of the medians, then each array has less than half of its elements **greater** than a and less than half of its elements **less** than b."? – dhruvbird Mar 19 '12 at 00:00
  • @dhruvbird: No, I meant what I wrote. The min of the medians is smaller than (or equal to) any median, so each array has _fewer_ than half its elements less than that. OK so I should have said "fewer" rather than "less" :-). But your variant has it backwards. (The overall algorithm still works because we throw away elements from the arrays _containing_ a and b... At least one of which will lose half its elements.) – Nemo Mar 19 '12 at 01:36
  • @Nemo Okay, I see your point now, but am unable to see the implication of less than half such elements being < a and > b in each array. I think the observation of the overall median being between the median of medians is more powerful and lets us discard elements from the ends of both arrays? – dhruvbird Mar 19 '12 at 12:38
  • The implication _is_that the overall median is between a and b. That is the key observation, but you have to prove it. That is what I meant by "proof goes something like this" – Nemo Mar 19 '12 at 13:12
  • @codeObserver could someone iterate through the example [3,7,9], [4,8,15], [2,3,9], [2,2,7] . I am strugling from last 30 mins – Kumar Roshan Mehta Oct 06 '17 at 21:34
  • 1
    I want to point out the calculation for lower median is not correct. For example, if an array has only 1 element then the lower median would be (1 / 2) -1 = -1, which is an invalid index. I think (N - 1) /2 is the correct way. – Zhengyang Mar 23 '18 at 19:55
  • Not sure why `throwing away k elements from the start or end of an array) is constant time.`, should not it cost `O(N)` if create new list? Unless use two indices to indicate list's start and end positions. And will the coding become a mess as there are 5 lists, how about 100 lists? – Yang Liu Oct 30 '21 at 03:58
1

Should be pretty straight to apply the same idea to 5 arrays.

First, convert the question to more general one. Finding Kth element in N sorted arrays

  1. Find (K/N)th element in each sorted array with binary search, say K1, K2... KN

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

  3. Throw away all elements less than Kmin or larger than Kmax, say X elements has been thrown away.

  4. Now repeat the process by find (K - X)th element in sorted arrays with remaining elements

Ryan Ye
  • 3,159
  • 1
  • 22
  • 26
  • Binary search alone is log(n), and you are doing it log(n) times, so this is O((log(n))^2). But you can solve this problem in O(log n) :-) – Nemo May 31 '11 at 03:13
  • 1
    Aach array is sorted, so you don't need binary search to find their (N/K)th smallest elements. They're just A1[N/K], A2[N/K], .... – JeffE Sep 07 '13 at 18:42
  • I don't see what use `Kmax` is in 1.&2.. You probably need _If 0 < (K-Xₙ) < N, find the (K-Xₙ)th among the first element of each sequence_. – greybeard Jan 01 '17 at 18:27
  • what is the K and X by the way? – Kumar Roshan Mehta Oct 06 '17 at 20:57
1

You don't need to do a complete merge of the 5 arrays. You can do a merge sort until you have (l+n+o+p+q)/2 elements then you have the median value.

karmakaze
  • 34,689
  • 1
  • 30
  • 32
  • 3
    You don't even need to do the sort -- just the comparisons. You only need to save the item halfway through all the members of the 5 sorted arrays. – xpda Jun 01 '11 at 04:00
0

Finding the kth element in a list of sorted lists can be done by binary search.

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)

The code above gives correct-statistical median as well.

Coupling this above binary search with patience-sort, gives a valuable technique.

There is worth mentioning median of median algorithm for selecting pivot. It gives approximate value. I guess that is different from what we are asking here.

KRoy
  • 1,290
  • 14
  • 10
0

Use heapq to keep each list's minum candidates.

Prerequisite: N sorted K length list

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'      
Yang Liu
  • 346
  • 1
  • 6