retagged by
13,043 views
32 32 votes
Give an optimal algorithm in pseudo-code for sorting a sequence of $n$ numbers which has only $k$ distinct numbers ($k$ is not known a Priori). Give a brief analysis for the time-complexity of your algorithm.

6 Answers

27 27 votes

Answer should be counting sort which will take $O(n+k)$ time.

See here:http://www.geeksforgeeks.org/counting-sort/

edited by
10 10 votes

We cannot do it in O(n+k). We will need O(n+klogk).

Why not O(n+k)?

In worst case, if we have k=n distinct elements, then by O(n+k) we are implying that n distinct elements can be sorted in O(n) – which is impossible (without some other special condition, such as range or anything, and there isn’t any such in this question). Here we don’t have the range given explicitly, so at the worst it can be 1 to m where m is the largest element and m can be larger than n itself (m can be O($n^{2}$) or O($n^{3}$) or even higher). So we cannot use the range 1 to m because a simple O(nlogn) solution would be way better than O(m). 

How in O(n+klogk)?

  1. Create a Hash map of all the distinct k elements (similar to the count array we use in counting sort)
  2. Sort these k elements –  this needs to be done explicitly because we have k distinct elements and not a range of 1 to k. This wasn’t needed in counting sort because we can take an array of size k and the indices will be in sorted order only (1 to k) but here this won’t hold true so sorting the distinct elements is needed  --- O(klogk)
  3. Rest is same as counting sort – we store the count of each distinct element and then print them in sorted manner  --- O(n)

So time complexity will be O(n + klogk)

3 3 votes
use a hash map with chaining to sort all the numbers in $\mathcal{O}(n)$ time and constant space.
1 1 vote

 We can write code like this
 

int distinctValue(int a[ ] , int n)
{
   int k=0,value[100],distinct[100];
   for(i=0 ; i<10 ; i++)
       {
       if(max<value[i])
          max=value[i];
       }
   for(i=0; i<max; i++)
       {
          distinct[value[i]]=value[i]; 
       }
    for(i=0; i<max; i++)
      {
         distinct[i]=distinct[i-1]+distinct[i];
      }
return 0;
}

 

So, complexity will be $O\left ( n \right )$

edited by
1 1 vote

 

Why Counting Sort is mathematically incorrect here:

Counting Sort has a time complexity of $O(n + M)$, where $n$ is the number of elements and $M$ is the difference between the maximum and minimum values in the array (the range).

  • The problem states there are $k$ distinct numbers, but it does not bound their values.

  • If your array is [1, 1000000000, 1, 1000000000], then $n = 4$ and $k = 2$. However, Counting Sort would require an auxiliary array of size $1,000,000,000$ and take $O(10^9)$ time. This is wildly inefficient and clearly not the intended "optimal" algorithm.

 

Approach : Worst-Case $O(n \log k)$

To achieve a strict worst-case bound, we use a self-balancing Binary Search Tree ..

Pseudo-code:

Algorithm SortKDistinct(A, n):
    Initialize an empty balanced BST (e.g., AVL Tree)
    
    // Step 1: Count frequencies using the BST
    For i = 1 to n:
        Node = BST.Search(A[i])
        If Node exists:
            Node.count = Node.count + 1
        Else:
            BST.Insert(A[i] with count = 1)
            
    // Step 2: Reconstruct the sorted array
    Index = 1
    For each Node in BST.InOrderTraversal():
        While Node.count > 0:
            A[Index] = Node.value
            Index = Index + 1
            Node.count = Node.count - 1
            
    Return A

Time Complexity Analysis:

  • Step 1: We insert/search $n$ elements into a BST. Because there are only $k$ distinct elements, the BST will never exceed a size of $k$. Therefore, the maximum height of the tree is $O(\log k)$. Processing $n$ elements takes exactly $O(n \log k)$ time.

  • Step 2: An in-order traversal of a BST with $k$ nodes takes $O(k)$ time. Writing the $n$ elements back to the array takes $O(n)$ time. Total for Step 2 is $O(n)$.

  • Overall Worst-Case Time Complexity: $O(n \log k) + O(n) = \mathbf{O(n \log k)}$.

  • Space Complexity: $\mathbf{O(k)}$ to store the BST.

Approach : Expected $O(n + k \log k)$ 

 we often use Hash Maps. We can count the frequencies in $O(n)$ expected time, extract the $k$ distinct keys, sort just those $k$ keys in $O(k \log k)$ time, and rebuild the array.

While practically faster because $O(n + k \log k) \le O(n \log k)$, algorithm purists often prefer the BST method for formal proofs because standard hashing has a worst-case $O(n^2)$ time if many collisions occur (


Deep Analysis: Why $O(n \log k)$ is theoretically optim

 

 if we know the array is a multiset (an array with duplicates) containing $n$ elements but only $k$ distinct values with frequencies $f_1, f_2, ..., f_k$, the number of possible unique permutations is:

$$\frac{n!}{f_1! \cdot f_2! \dots f_k!}$$

Taking the base-2 logarithm of this value (using Stirling's approximation) gives us the minimum number of comparisons required to sort it. Mathematically, this bounds to exactly $\Omega(n \log k)$ in the worst case (where each of the $k$ elements appears $n/k$ times).

Therefore, the $O(n \log k)$ balanced BST algorithm is not just a good idea; it is provably, mathematically optimal for comparison-based sorting of multisets with unknown element bounds.

 

1. Mathematical Derivation: How $n \log k$ Occurs

The number of comparisons required to sort a sequence is determined by the Entropy of the possible permutations. In a standard sort of $n$ unique elements, there are $n!$ permutations, and $\log_2(n!) \approx n \log n$.

However, in a multiset with $n$ total elements and $k$ distinct elements with frequencies $f_1, f_2, \dots, f_k$, the number of distinct permutations $W$ is given by the multinomial coefficient:

$$W = \frac{n!}{f_1! \cdot f_2! \dots f_k!}$$

To find the minimum comparisons ($C$), we take the base-2 logarithm and use Stirling’s Approximation ($\ln n! \approx n \ln n - n$):

$$\log_2 W = \log_2(n!) - \sum_{i=1}^{k} \log_2(f_i!)$$

$$\log_2 W \approx (n \log_2 n - n) - \sum_{i=1}^{k} (f_i \log_2 f_i - f_i)$$

Since $\sum f_i = n$, the "$-n$" and "$-(-n)$" terms cancel out:

$$\log_2 W \approx n \log_2 n - \sum_{i=1}^{k} f_i \log_2 f_i$$

Now, let $p_i = \frac{f_i}{n}$ be the probability of picking the $i$-th distinct element. Substituting $f_i = n p_i$:

$$\log_2 W \approx n \log_2 n - \sum_{i=1}^{k} (n p_i) \log_2 (n p_i)$$

$$\log_2 W \approx n \log_2 n - \left[ \sum n p_i \log_2 n + \sum n p_i \log_2 p_i \right]$$

$$\log_2 W \approx n \log_2 n - n \log_2 n \underbrace{(\sum p_i)}_{1} - n \sum p_i \log_2 p_i$$

$$\log_2 W \approx n \left( -\sum_{i=1}^{k} p_i \log_2 p_i \right) = n \cdot H$$

Where $H$ is the Shannon Entropy of the frequency distribution.

  • The Worst Case: Entropy $H$ is maximized when all $k$ elements are equally frequent ($p_i = 1/k$).

  • In this case, $H = -\sum \frac{1}{k} \log_2 \frac{1}{k} = \log_2 k$.

  • Finally : The lower bound is $\Omega(n \log k)$.


 

edited by
0 0 votes
To say the complexity is linear would be assuming that not only one knows the value of k, but also the relative order of k distinct elements. (If not? How would you order k keys?)

If you use hashing by chaining, you would still take $n*k*k$ time at max.

 

The correct way to do this would be to use a BST with an additional count field to perform a counting sort, giving you the complexity of $n*\log{k}$
Position:
Show:

Related questions

11 11 votes
2 2 answers
3.0k
3.0k views
Kathleen asked Sep 12, 2014
3,019 views
Using $\text{D}$ flip-flop gates, design a parallel-in/serial-out shift register that shifts data from left to right with the following input lines:Clock $\text{CLK}$Thre...
50 50 votes
4 answers 4 answers
9.9k
9.9k views
Kathleen asked Sep 12, 2014
9,903 views
Match the pairs in the following questions by writing the corresponding letters only.$$\begin{array}{|c|l|c|l|} \hline A. & \text{The number of distinct binary tree} & P....
27 27 votes
6 answers 6 answers
12.9k
12.9k views
Kathleen asked Sep 12, 2014
12,916 views
Indicate all the true statements from the following:Recursive descent parsing cannot be used for grammar with left recursion.The intermediate form for representing expres...
66 66 votes
6 answers 6 answers
19.4k
19.4k views
Kathleen asked Sep 12, 2014
19,408 views
The minimum number of comparisons required to sort $5$ elements is ______