Problem overview
The method KSmallest receives an int[] called nums and a count k. It must return the k smallest values as a new int[]. The order of the result doesn't matter, so for nums = [5, 9, 3, 6, 2, 1, 3, 2, 7, 5] and k = 4, both [1, 2, 2, 3] and [2, 1, 3, 2] are correct.
Duplicates count separately. The array holds the value 2 twice, and the answer contains it twice, because the 2nd and 3rd smallest elements are both 2.
Approach: sort, then take the front
If the array is sorted in ascending order, the smallest values are simply its first k elements. That reduces the problem to two operations: sort, then copy a prefix.
The statement asks for O(n log n) time, which is exactly the cost of a comparison sort. Copying the first k elements costs O(k), and k is at most n, so the sort dominates. Faster approaches exist, such as a heap or a partition-based selection, but this lesson uses the simplest idea.
Here is the idea on the example. After sorting, nums is [1, 2, 2, 3, 3, 5, 5, 6, 7, 9]. The first four elements are 1, 2, 2, 3, which is the expected answer.
Step by step
- Sort in place.
Array.Sort(nums)reorders the array itself and returns nothing. For anint[]it sorts in ascending order, so no comparer is needed. - Return the first
kelements.nums.Take(k)describes "the firstkelements", andToArray()copies them into theint[]that the method promises to return.
The solution in C#
class Solution {
public int[] KSmallest(int[] nums, int k) {
Array.Sort(nums);
return nums.Take(k).ToArray();
}
}
The C# details worth knowing:
Takecomes from LINQ. It is an extension method inSystem.Linq, so a program that uses it needsusing System.Linq;.Array.Sortlives inSystem.Takeis lazy. It doesn't copy anything by itself. It produces the elements one by one when something asks for them, andToArray()is the call that asks for all of them and stores them in a new array.- The input array changes.
Array.Sortsortsnumsin place, so the caller's array is sorted after the call. If the original order matters elsewhere, sort a copy instead. Takeis forgiving aboutk. Ifkis larger than the array,Takereturns all the elements instead of throwing. Ifkis 0 or negative, it returns an empty sequence.- Sorting is not stable for equal values, and it doesn't need to be. Equal integers are indistinguishable, so the result is the same whichever copy of a value comes first.
Complexity
Time: O(n log n). Array.Sort is a comparison sort, so it costs O(n log n). Taking the first k elements is O(k), which never exceeds O(n).
Space: the sort works inside the input array and needs no second array. The only allocation of note is the returned array of k elements, which the method has to produce anyway.
Edge cases and common mistakes
- Duplicates. Don't remove them. For
[2, 2, 1]andk = 2, the answer is[1, 2], and for[2, 2, 2]andk = 2it is[2, 2]. kequal to the length. The result is the whole array, sorted.klarger than the length.Takereturns every element rather than failing. A hand-written loop that copieskelements would throwIndexOutOfRangeExceptioninstead.- Forgetting
ToArray().Take(k)returns anIEnumerable<int>, not anint[], so returning it directly doesn't compile. - Taking the wrong end. After an ascending sort the smallest values are at the front. Taking from the back would give the largest.
Testing
nums = [5, 9, 3, 6, 2, 1, 3, 2, 7, 5],k = 4gives[1, 2, 2, 3].nums = [5, 9, 3],k = 10gives[3, 5, 9].nums = [5, 9, 3],k = 0gives[].