16

This is a standard question which has been answered many a times in several sites but in this version there are additional constraints:

  1. The array is read only (we cannot modify the array).
  2. Do it in O(1) space.

Can someone please explain to me the approach to this in best possible time complexity.

shapiro yaacov
  • 2,308
  • 2
  • 26
  • 39
Mohd Waseem
  • 1,244
  • 2
  • 15
  • 36
  • Is `k` constant? It can be done in O(k) space without modifying the array – amit Jun 09 '15 at 09:38
  • Lots of good ideas at http://stackoverflow.com/questions/251781/how-to-find-the-kth-largest-element-in-an-unsorted-array-of-length-n-in-on?rq=1 But i agree with @amit's observation. To do it in O(k) space is trivial, but I doubt that its possible with only O(1) space & being unable to change the array. – Mark Gossage Jun 09 '15 at 09:46
  • @amit : k is provided as input.it can be between 1 and n. – Mohd Waseem Jun 09 '15 at 09:49

3 Answers3

31

There is actually one way of solving this problem in O(n log d) time complexity & O(1) space complexity, without modifying the array. Here n stands for the length of the array, while d is the length of the range of numbers contained in it.

The idea is to perform a binary search for the kth smallest element. Start with lo = minimum element, and hi = maximum element. In each step check how many elements are smaller than mid and update it accordingly. Here is the Java code for my solution:

    public int kthsmallest(final List<Integer> a, int k) {
        if(a == null || a.size() == 0)
             throw new IllegalArgumentException("Empty or null list.");
        int lo = Collections.min(a);
        int hi = Collections.max(a);

        while(lo <= hi) {
            int mid = lo + (hi - lo)/2;
            int countLess = 0, countEqual = 0;

            for(int i = 0; i < a.size(); i++) {
                if(a.get(i) < mid) {
                    countLess++;
                }else if(a.get(i) == mid) {
                    countEqual++;
                }
                if(countLess >= k) break;
            }

            if(countLess < k && countLess + countEqual >= k){
                return mid;
            }else if(countLess >= k) {
                hi = mid - 1;
            }else{
                lo = mid + 1;
            }
        }


        assert false : "k cannot be larger than the size of the list.";
        return -1;
    }

Note that this solution also works for arrays with duplicates and/or negative numbers.

Martin Copes
  • 931
  • 1
  • 7
  • 14
  • 1
    This is more `O(n log d)` where `d = max|a_i - a_j|`. With `Integer`s it boils down to just `O(n)`. Good idea :) – Gil Vegliach Jan 01 '17 at 00:33
  • 2
    @GilVegliach You are right. Thank you! I updated the answer. – Martin Copes Jan 01 '17 at 00:34
  • The solution is amazing. But wanted to understand how you came up with this solution? – Shubhankar Raj Jul 17 '18 at 14:28
  • Great Solution. We basically take a range from `arrayMin` to `arrayMax` and keep on dividing it half until we get number of elements less than equal to mid as k. Count equal ensures we make sure that mid is in the list. : `if(countLess < k && countLess + countEqual >= k){` We keep on dividing problem by half of (arrayMax - arrayMin) ans need to traverse whole array each time so `O(n log d )` Time complexity. Great idea.!! – Rishabh Agarwal Mar 11 '19 at 17:16
  • When `countLess == k - 1` and `countEqual == 0`, i.e. the current `mid` is not present in the array, your solution will do `lo = mid + 1`. However, is it not possible that the solution was actually to the left of the current `mid`? – whateven Jun 24 '20 at 13:10
12

I am going to assume read-only array is a strong requirement, and try to find tradeoffs that do not violate it in this answer (So, using selection algorithm is not an option, since it modifies the array)

As a side note, from wikipedia, it cannot be done in O(1) space without modifying the array:

The required space complexity of selection is easily seen to be k + O(1) (or n − k if k > n/2), and in-place algorithms can select with only O(1) additional storage .... The space complexity can be reduced at the cost of only obtaining an approximate answer, or correct answer with certain probability

It can be done in O(k) space with O(nlogk) time. In case k is constant, it is O(1) solution

The idea is to keep a max heap of size k. First populate it with the first k elements, and then keep iterating the array. If some element x is smaller than the top of the heap, pop the old head, and insert x instead.

When you are done, the head of the heap is the kth smallest element.


In terms of big O notation, it can be done in O(n) with O(k) space by using a new array of size 2k, load elements in chunks to the array, and use selection algorithm on this auxillary array to find the kth element. Discard all elements bigger than the k'th element, and repeat for next chunk (load more k elements). Complexity is O(k*n/k) = O(n)

This is however seldom used and has significantly worse constants than the heap solution, which is used quite often.


If you really want to use O(1) space, a brute force solution can be used by finding a minimum k times. You only need to remember the old minimum, which is constant space. This solution is O(nk) time with O(1) space, which is significantly less efficient than the alternatives, in terms of time.

amit
  • 175,853
  • 27
  • 231
  • 333
  • yes brute force approach in O(nk) time will do . but what if the array contains duplicate elements ?? – Mohd Waseem Jun 09 '15 at 09:56
  • 1
    @MohdWaseem Then store (value,index), and skip elements that are smaller than value, or equal to value and smaller/equal to index – amit Jun 09 '15 at 09:56
  • thanks for the approach but it is only getting partially accepted and failing rest of the test cases due to tle. – Mohd Waseem Jun 09 '15 at 10:29
  • solution using max_heap gives memory limit exceed. – Mohd Waseem Jun 09 '15 at 10:36
  • @MohdWaseem And you are sure you cannot touch the original array? (if you can selection algorithm that I linked in the beginning is the way to go). – amit Jun 09 '15 at 10:43
  • @MohdWaseem - use brute force approach storing 2 integers, first is the smallest found, the second is the number of occurrences of the first. You'll then do at most `k` iterations of the brute force loop, and fewer if you find ties. – Rob Jun 09 '15 at 10:48
  • @amit yes i am sure.I solved it by first copying the whole array to another non const array then applying selection algorithm to find kth smallest number. that gives memory limit exceed. this is the declaration of the function: " int Solution::kthsmallest(const vector &A, int k) " ... – Mohd Waseem Jun 09 '15 at 11:00
  • @MohdWaseem My C++ is rusty, but `const vecrtor &A` - doesn't it mean you cannot change what `A` refers to, but can change the order of the elements themselves? (Again, could be talking nonesense, haven't used C++ for some long time now, and even when I did I was not an expert). – amit Jun 09 '15 at 11:05
  • @Rob but O(nk) is only partially accepted. A somewhat similar approach was suggested by amit in previous comments.cant we do it in strictly O(1) space and O(nlogk) or better time ?? – Mohd Waseem Jun 09 '15 at 11:06
  • @amit I think it means we cannot modify the elements in the vector. i am sorry amit but i am also a begginer in c++. but i think you might be mixing this up with int * const and int const * where former means constant pointer and later means pointer to a constant int.. – Mohd Waseem Jun 09 '15 at 11:24
  • The k-element heap approach would not work for arrays having repeated elements. Code here - http://codepad.org/aPFgGVuA – Ayush Goel Nov 06 '16 at 12:06
  • @amit You contradict yourself when you say "it cannot be done in O(1) space without modifying the array...". However later you propose a brute force solution that takes O(1) space and does not modify the array. – Martin Copes Dec 30 '16 at 19:10
  • @MohdWaseem if you are interested in an O(n log n) solution with constant space complexity and that does not modify the input, then check my post. – Martin Copes Dec 30 '16 at 19:11
0

I've seen this problem on InterviewBit and I guess I've the solution right, captured the logic I followed to find the 'k'th smallest element in array below..

  • Find the smallest element in the array, this is the last smallest.
  • In a loop, for 'k' times, find the element that is next biggest to the smallest found in the last iteration of the loop(the last smallest found in first step will be used in the first iteration of the loop)
  • Return the last found next biggest element from the loop Copied the code below..


int getSmallest(const int* A, int nSize){
        int nSmallestElemIndex = -1;
        int i;
        for(i=0; i < nSize; i++){
            if (-1 == nSmallestElemIndex){
                nSmallestElemIndex = i;
                continue;
            }
            if (A[i] < A[nSmallestElemIndex]){
                nSmallestElemIndex = i;
            }
        }
        return nSmallestElemIndex;
    }

    int nextBig(const int* A, int nSize, int nCurrentSmallest){
        int nNextBig = -1;
        int i;
        for(i=0; i < nSize; i++){
            if (i == nCurrentSmallest || A[i] < A[nCurrentSmallest]){
                continue;
            }
            if (A[i] == A[nCurrentSmallest] && 
                i <= nCurrentSmallest){
                continue;
            }

            if (nNextBig == -1){
                nNextBig = i;
                continue;
            }
            if (A[i] >= A[nCurrentSmallest] && A[i] < A[nNextBig]){
                nNextBig = i;
            }
        }
        return nNextBig;
    }

    int kthsmallest(const int* A, int n1, int B) {
        int nSmallestElem = getSmallest(A, n1);
        int nCount = 1;
        int nRes = nSmallestElem;
        while(nCount < B){
            nRes = nextBig(A, n1, nRes);
            nCount++;
        }
        return nRes;
    }

I executed and validated it for the test cases on my machine but it's not accepted at Interviewbit.

I'll be glad if the solution passes your validation. Let me know your comments.