Problem overview
The method sort receives an Array[Int] named nums and must sort it in ascending order. It returns Unit: the array is sorted in place, and the caller sees the result in the same array. For Array(3, 1, 3, 2, 5, 4) the array becomes 1, 2, 3, 3, 4, 5. Duplicates stay, as the two 3s show.
The statement asks for O(n^2) time and O(1) extra space, and the lesson is built on Selection Sort.
Approach: fill the array from the left
Selection Sort works position by position. Position 0 should end up holding the smallest value, position 1 the second smallest, and so on. When the pass for position i is finished, everything before i is in its final place, so later passes only look at the part to the right.
The pass for position i compares nums(i) with every later element nums(j). When a later element is smaller, it must come to position i, so the two are swapped. By the end of the inner loop, nums(i) holds the smallest value from index i onward.
The version in this lesson swaps whenever it sees a smaller element, so one pass can swap several times. The classic textbook version first finds the position of the minimum and swaps once at the end of the pass. Both end with the same sorted array, and both are O(n^2).
Why does it work? After the pass for position i, no element to the right of i is smaller than nums(i): any smaller value met during the pass was swapped into position i, and the value that ends up there is compared with every remaining element. So position i holds the smallest of the elements from i onward, and the sorted prefix grows by one each pass. After the last pass the whole array is sorted. Nothing is ever swapped into the prefix, because every swap involves positions i and j with j > i.
The example, traced
On Array(3, 1, 3, 2, 5, 4):
i = 0. 3 > 1 is true, so they swap:1, 3, 3, 2, 5, 4. The 1 is now at position 0, and nothing later is smaller, so there are no more swaps in this pass.i = 1. 3 > 3 is false. Then 3 > 2 is true, so they swap:1, 2, 3, 3, 5, 4.i = 2andi = 3. No later element is smaller. Nothing changes.i = 4. 5 > 4 is true, so they swap:1, 2, 3, 3, 4, 5.
Three swaps were enough here.
Step by step
- Outer loop. Use
i <- 0 until nums.lengthto visit every position. Each pass fills positioni. - Inner loop. Inside it use
j <- (i + 1) until nums.length. Positions beforeiare final, so only the indices afteriare compared. - Compare. Test
nums(i) > nums(j): a smaller value later in the array belongs at positioni. Using>and not>=means equal values are never swapped, which saves useless work. - Write
swap. Add a helper abovesortthat takesnums,iandjand returnsUnit. It savesnums(i)in aval temp, copiesnums(j)intonums(i), then putstempintonums(j). Withouttemp, the first assignment would destroy the old value ofnums(i). - Call it. Inside the
if, callswap(nums, i, j).
The solution in Scala
class Solution {
def swap(nums: Array[Int], i: Int, j: Int): Unit = {
val temp = nums(i)
nums(i) = nums(j)
nums(j) = temp
}
def sort(nums: Array[Int]): Unit = {
for (i <- 0 until nums.length) {
for (j <- (i + 1) until nums.length) {
if (nums(i) > nums(j)) {
swap(nums, i, j)
}
}
}
}
}
Scala details:
- Arrays are mutable and passed by reference.
swapreceives the same array object thatsortholds, so its assignments are visible afterwards. Method parameters themselves can't be reassigned, but the contents of an array can. - Assignment with parentheses.
nums(i) = nums(j)is shorthand for a call of the array'supdatemethod. It works becauseArrayis mutable; an immutableListhas no such assignment. - No return value. A
Unitmethod that ends with aforloop returns(). The sorted data is innums. - Index ranges.
untilexcludes the upper bound. For the lastithe range(i + 1) until nums.lengthis empty, and the inner loop is skipped. - Library sorting.
nums.sortedreturns a new sorted array, andscala.util.Sorting.quickSort(nums)sorts in place. The lesson writes the algorithm out so you can see how it works.
Complexity
Time: O(n^2). The inner loop makes n - 1 comparisons for i = 0, n - 2 for i = 1, and so on, which is n * (n - 1) / 2 comparisons whatever the order of the input. Swaps add more constant-time work. In this version a reversed array is the worst case for swaps: Array(5, 4, 3, 2, 1) makes 10 swaps, while the classic one-swap-per-pass version would make at most 4.
Space: O(1). The array is rearranged in place, and the extra memory is the loop variables and temp.
Edge cases and common mistakes
- Empty and one-element arrays. There is nothing to compare, and the array is unchanged.
- Already sorted input. No comparison succeeds, so there are no swaps, though the comparisons still happen.
- Duplicates.
Array(2, 2, 2)makes no swaps because>is false for equal values. - Comparing the wrong direction. Using
<sorts in descending order. - Starting
jat 0. That would compare elements to the left too, and it would swap values back and forth, undoing the sorted prefix. - Skipping
temp. Writingnums(i) = nums(j)followed bynums(j) = nums(i)leaves two copies of the same value. - Expecting a result.
sortreturnsUnit; read the array afterwards.
Testing
Array(3, 1, 3, 2, 5, 4)becomes1, 2, 3, 3, 4, 5.Array(5, 4, 3, 2, 1)becomes1, 2, 3, 4, 5.Array(9)stays9.Array()stays empty.
