![]() |
VOOZH | about |
Given an array of distinct integers arr[] and an integer k. The task is to find the k-th smallest element in the array. For better understanding, k refers to the element that would appear in the k-th position if the array were sorted in ascending order.
Note: k will always be less than the size of the array.
Examples:
Input: arr[] = [7, 10, 4, 3, 20, 15], k = 3
Output: 7
Explanation: The sorted array is [3, 4, 7, 10, 15, 20]. The 3rd smallest element is 7.Input: arr[] = [12, 3, 5, 7, 19], k = 2
Output: 5
Explanation: The sorted array is [3, 5, 7, 12, 19]. The 2nd smallest element is 5.Input: arr[] = [1, 5, 2, 8, 3], k = 4
Output: 5
In the previous post, we explored an algorithm with expected linear time complexity. In this post, a worst-case linear time we method is discussed.
The intuition of this code starts with the same base idea as QuickSelect(), to find the k-th smallest element by partitioning the array around a pivot. But unlike QuickSelect, which may choose a bad pivot and degrade to O(n²) in the worst case, this algorithm ensures worst-case linear time by carefully choosing a pivot using the Median of Medians technique.
We want a pivot that guarantees a reasonably balanced partition, not perfectly balanced, but not extremely skewed either. That means the pivot should ensure that a significant portion of the array lies on both sides of it. This is where the Median of Medians strategy comes in
Steps to implement the above idea:
7
Time Complexity: O(n), Worst-case linear time for selection
Space Complexity: O(n), Extra space for storing medians recursively in each level of selection calls.
We analyze the worst-case time complexity of the Median of Medians algorithm step by step:
To understand the size of the recursive call, we analyze how many elements are guaranteed to be greater or smaller than the pivot. At least half of the n/5 medians are greater than or equal to the median of medians. Each of those groups contributes at least 3 elements greater than the median of medians. Therefore, the number of elements greater than or equal to the pivot is at least (3 * ceil(1/2 * ceil(n/5)) - 6), which is at least (3n/10 - 6). Similarly, the number of elements smaller than the pivot is at least 3n/10 - 6.
So, in the worst case, the recursive call goes to at most n - (3n/10 - 6) = 7n/10 + 6 elements.
The recurrence relation becomes:
T(n) <= Θ(1), if n <= 80
T(n) <= T(ceil(n/5)) + T(7n/10 + 6) + O(n), if n > 80We prove T(n) = O(n) by substitution. Assume T(n) <= cn for some constant c and for all n > 80.
Substituting into the recurrence:
T(n) <= c(n/5) + c(7n/10 + 6) + O(n)
= cn/5 + 7cn/10 + 6c + O(n)
= 9cn/10 + 6c + O(n)We can choose c large enough such that cn/10 >= 6c + O(n), so T(n) <= cn.
Hence, the worst-case running time is linear, O(n).
Although this algorithm is linear in the worst case, the constants involved are large due to recursive overhead and multiple passes. In practice, a randomized QuickSelect is much faster and is generally preferred despite its average-case nature.