Kth Smallest
Given an array arr[] and an integer k where k is smaller than the size of the array, the task is to find the kth smallest element in the given array.
Follow up: Don't solve it using the inbuilt sort function.
Examples :
Input: arr[] = [7, 10, 4, 3, 20, 15], k = 3
Output: 7
Explanation: 3rd smallest element in the given array is 7.
Input: arr[] = [2, 3, 1, 20, 15], k = 4
Output: 15
Explanation: 4th smallest element in the given array is 15.
Constraints:
1 <= arr.size <= 106
1<= arr[i] <= 106
1 <= k <= n
To solve the problem of finding the kth smallest element in an array without using built-in sorting functions, we can use a heap-based approach, particularly min-heap or max-heap.
Approach:
-
Min-Heap Approach:
- We can build a min-heap of size
k
. - Then, for each element in the array:
- If the heap size is less than
k
, we push the element into the heap. - If the heap size exceeds
k
, we pop the minimum element from the heap.
- If the heap size is less than
- After processing all elements, the root of the heap will contain the
k
th smallest element. - Time complexity: The heap operations (insert and delete-min) take , and we do this for elements, so the overall time complexity is .
- We can build a min-heap of size
-
Counting Sort Approach (For a restricted range of values):
- Given the problem constraints, we can also solve this using a counting sort-like approach. We count the frequency of each element and iterate through the frequencies to find the kth smallest.
- Time complexity: This approach runs in , where is the maximum value in the array. However, this approach may not be feasible if the maximum value in the array is too large.
Solution using Min-Heap Approach in C#:
using System;
using System.Collections.Generic;
public class Solution {
public int KthSmallest(int[] arr, int k) {
// Step 1: Create a max-heap with size k
PriorityQueue<int, int> maxHeap = new PriorityQueue<int, int>();
// Step 2: Insert first k elements into the heap
for (int i = 0; i < k; i++) {
maxHeap.Enqueue(arr[i], -arr[i]); // We use -arr[i] to simulate max-heap (since PriorityQueue is min-heap by default)
}
// Step 3: Process the remaining elements
for (int i = k; i < arr.Length; i++) {
if (arr[i] < -maxHeap.Peek()) { // Compare with the root of the heap
maxHeap.Dequeue(); // Remove the largest element in the heap
maxHeap.Enqueue(arr[i], -arr[i]); // Add the new smaller element
}
}
// Step 4: The root of the heap is the kth smallest element
return -maxHeap.Peek();
}
}
// Driver code to test
public class Program {
public static void Main(string[] args) {
Solution sol = new Solution();
int[] arr1 = { 7, 10, 4, 3, 20, 15 };
int k1 = 3;
Console.WriteLine("The 3rd smallest element is: " + sol.KthSmallest(arr1, k1)); // Output: 7
int[] arr2 = { 2, 3, 1, 20, 15 };
int k2 = 4;
Console.WriteLine("The 4th smallest element is: " + sol.KthSmallest(arr2, k2)); // Output: 15
}
}
Explanation:
-
PriorityQueue:
- A max-heap is implemented using a PriorityQueue in C# (which is a min-heap by default, so we use negative values to simulate the max-heap behavior).
-
First k elements:
- We add the first
k
elements to the heap.
- We add the first
-
Remaining elements:
- For each element from
k
onwards, we check if it is smaller than the root of the heap (i.e., the largest of thek
smallest elements). - If so, we remove the largest element from the heap and add the current element to the heap.
- For each element from
-
Result:
- After processing all elements, the root of the heap will be the
k
th smallest element.
- After processing all elements, the root of the heap will be the
Time and Space Complexity:
- Time Complexity:
- Building the heap initially takes .
- For each of the remaining elements, we perform a heap operation, which takes . Therefore, the total time complexity is .
- Space Complexity:
- The space complexity is , due to the heap storing up to
k
elements.
- The space complexity is , due to the heap storing up to
Example Walkthrough:
Example 1:
- Input:
arr[] = [7, 10, 4, 3, 20, 15], k = 3
- First, we build the heap with the first 3 elements:
[7, 10, 4]
(max-heap). - Then we process the remaining elements:
- For
3
: it's smaller than the root (10
), so replace10
with3
. Heap becomes[7, 3, 4]
. - For
20
: it's larger than the root (7
), so no change. - For
15
: it's larger than the root (7
), so no change.
- For
- The root of the heap is
7
, which is the 3rd smallest element.
Example 2:
- Input:
arr[] = [2, 3, 1, 20, 15], k = 4
- After building the heap with the first 4 elements:
[2, 3, 1, 20]
. - Then we process the remaining element
15
, and since15
is smaller than20
, we replace20
with15
. - The root of the heap is
15
, which is the 4th smallest element.
Conclusion:
This solution efficiently solves the problem of finding the kth smallest element using a max-heap of size k
. The time complexity is optimal for the problem, making it suitable for large arrays with constraints up to .
For the kth smallest element problem, I've covered the heap-based approach, which is an efficient solution. However, here are a few other approaches that can be considered, along with their time complexities:
1. Sorting Approach (Using Sorting)
- Method: Sort the array in non-decreasing order and pick the element at index
k-1
. - Time Complexity: (due to sorting).
- Space Complexity: (if sorting in place).
This is the simplest approach but not the most efficient when compared to the heap-based solution.
using System;
public class Solution {
public int KthSmallest(int[] arr, int k) {
Array.Sort(arr);
return arr[k - 1];
}
}
2. QuickSelect (Partition-based approach)
- Method: Use the QuickSelect algorithm, a variation of QuickSort, to find the
k
th smallest element. This is more efficient on average than sorting because it doesn't need to fully sort the array. - Time Complexity: Average case , Worst case (similar to QuickSort).
- Space Complexity: .
QuickSelect works by partitioning the array, much like in QuickSort. Instead of sorting both sides, it continues searching in the side that contains the k
th smallest element.
using System;
public class Solution {
public int KthSmallest(int[] arr, int k) {
return QuickSelect(arr, 0, arr.Length - 1, k - 1);
}
private int QuickSelect(int[] arr, int left, int right, int k) {
if (left == right) return arr[left];
int pivotIndex = Partition(arr, left, right);
if (k == pivotIndex) return arr[k];
else if (k < pivotIndex) return QuickSelect(arr, left, pivotIndex - 1, k);
else return QuickSelect(arr, pivotIndex + 1, right, k);
}
private int Partition(int[] arr, int left, int right) {
int pivot = arr[right];
int i = left;
for (int j = left; j < right; j++) {
if (arr[j] <= pivot) {
Swap(arr, i, j);
i++;
}
}
Swap(arr, i, right);
return i;
}
private void Swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
3. Heap-based Approach (as explained previously)
- Method: Use a min-heap or max-heap to efficiently find the
k
th smallest element. - Time Complexity: (for inserting elements into the heap).
- Space Complexity: (for the heap).
As described earlier, we can use a max-heap to track the k
smallest elements, and the root of the heap will give us the k
th smallest element.
4. Counting Sort (For a Restricted Range)
- Method: Count the frequency of each element in the array and use that to determine the
k
th smallest element. - Time Complexity: , where is the maximum value in the array (or range of values).
- Space Complexity: for storing the count.
This approach works well when the range of elements is small (for example, if the values are integers between 1 and 100). For larger ranges, this approach might not be feasible.
using System;
public class Solution {
public int KthSmallest(int[] arr, int k) {
int maxVal = 0;
foreach (var num in arr) {
maxVal = Math.Max(maxVal, num);
}
int[] count = new int[maxVal + 1];
foreach (var num in arr) {
count[num]++;
}
int countSoFar = 0;
for (int i = 0; i <= maxVal; i++) {
countSoFar += count[i];
if (countSoFar >= k) {
return i;
}
}
return -1; // If no such element exists
}
}
Conclusion:
For each problem, it's good practice to consider multiple approaches and choose the one that best suits the problem's constraints, especially for large datasets:
- Sorting is simple but slower for large arrays.
- QuickSelect is more efficient for average cases but has a worst-case time complexity.
- Heap-based approaches work well when
k
is small relative ton
. - Counting Sort is useful when the range of numbers is constrained.