Problem overview
The method kSmallest receives an array nums of positive integers and a number k. It returns the k smallest values, in any order.
For nums = [5, 9, 3, 6, 2, 1, 3, 2, 7, 5] and k = 4, the sorted array is [1, 2, 2, 3, 3, 5, 5, 6, 7, 9], so the four smallest values are 1, 2, 2, 3. Duplicates count separately: the second 2 is the third smallest value. The order of the answer doesn't matter, so [2, 1, 3, 2] would be accepted too.
Approach: sort, then take a prefix
If the array is in ascending order, the k smallest values are simply the first k elements. Sorting turns "find the smallest k" into "cut off the front of an array". Sorting costs O(n log n) and slicing costs O(k), so the total is O(n log n), which is the budget the statement sets.
The statement also mentions O(1) extra space. Sorting a copy of the array, as the reference solution does, uses O(n) extra memory, so it only fits that budget loosely. Sorting nums in place with scala.util.Sorting.quickSort(nums) and slicing afterwards would avoid the copy. Faster methods that avoid sorting everything exist, and the lesson leaves them for later.
Step by step
- Sort the numbers. Calling
sortedon an array returns a new array in ascending order and leavesnumsas it was. Store it insortedNums. - Return the first
kelements.slice(0, k)takes the elements from index 0 up to, but not including, indexk. That is exactly k elements.
The solution in Scala
class Solution {
def kSmallest(nums: Array[Int], k: Int): Array[Int] = {
val sortedNums = nums.sorted
return sortedNums.slice(0, k)
}
}
The Scala details worth knowing:
sortedis non-destructive. After the call,numsstill holds its original order, andsortedNumsis a separate array. Usevalbecause the name is never reassigned.slice(from, until)excludes theuntilindex.sortedNums.take(k)gives the same result and reads a little more directly.sliceis forgiving about bounds. Ifkis larger than the array, you get the whole array back instead of an exception. Withk = 0you get an empty array.returnis optional. The last expression of a method is its value, sosortedNums.slice(0, k)alone would work. The lesson's code usesreturn, which is also valid here, because it is directly in the method body.- Arrays print as references. To see the result, use
result.mkString(", "). Callingprintlnon the array itself shows an object reference.
Complexity
Time: O(n log n). The sort dominates. The slice copies k elements, and k is at most n.
Space: O(n) with this solution, because sorted allocates a second array, plus O(k) for the result. Sorting in place would bring the extra space down to the result alone.
Edge cases and common mistakes
- Duplicates. They are not merged. For
[2, 2, 5]andk = 2the answer is[2, 2]. klarger than the array.[3, 1]withk = 5returns[1, 3], both elements.k = 0. The result is an empty array.- Taking from the wrong end. The first k elements of the ascending array are the smallest. The last k are the largest.
- Expecting
numsto be sorted afterwards. It isn't. OnlysortedNumsis. slice(0, k - 1). This is off by one and returns only k - 1 elements.
Testing
nums = [5, 9, 3, 6, 2, 1, 3, 2, 7, 5],k = 4gives[1, 2, 2, 3].nums = [3, 1],k = 5gives[1, 3].nums = [3, 1],k = 0gives an empty array.
