Problem overview
The method distinctValues receives an array of integers and returns how many different values it contains. For [1, 5, -3, 1, -4, 2, -4, 7, 7] the distinct values are 1, 5, -3, -4, 2, 7, so the answer is 6.
Approach: sort so that equal values are neighbours
Checking whether each value has appeared before would mean comparing it with all earlier elements, which is O(n²). Sorting removes that work. In a sorted array, equal values sit next to each other, so a value is new exactly when it differs from the element just before it.
That gives a one-pass count. Start with 1, since the first element is always a new value, and add 1 each time an element differs from its predecessor. The sort costs O(n log n), the pass costs O(n), and the total is O(n log n), as the statement asks. Faster ways to count distinct values come in later lessons.
For the example, the sorted array is [-4, -4, -3, 1, 1, 2, 5, 7, 7]. The element changes at -3, 1, 2, 5 and 7, which makes 5 increments on top of the starting 1: 6 distinct values.
Step by step
- Sort
numsin place withscala.util.Sorting.quickSort, so equal values end up adjacent. - Start the counter at 1. It is a
varbecause it changes. The first element is always a new value. - Loop from the second element.
1 until nums.lengthmakesirun from 1 to the last index, sonums(i - 1)always exists. - Compare with the previous element. When
nums(i)differs fromnums(i - 1), a new value starts here, so add 1 to the counter. - Return the counter after the loop.
The solution in Scala
class Solution {
def distinctValues(nums: Array[Int]): Int = {
scala.util.Sorting.quickSort(nums)
var distinct = 1
for (i <- 1 until nums.length) {
if (nums(i) != nums(i - 1)) {
distinct += 1
}
}
return distinct
}
}
The Scala details worth knowing:
quickSortsorts in place. It lives inscala.util.Sortingand rearranges the array it is given. That is why the extra space stays small, but it also means the caller's array is changed.nums.sortedwould return a new array and leavenumsalone, at the cost of a copy.- No
++operator. Scala has no increment operator, so the counter grows withdistinct += 1. - Parentheses for indexing. Elements are read as
nums(i). - Ranges exclude their end with
until.1 until nums.lengthstops at the last valid index.1 to nums.lengthwould includenums.lengthand fail with anArrayIndexOutOfBoundsException. !=compares values. For integers, and for strings, it asks whether the contents differ.returnis optional at the end of a method. Writing justdistinctwould return the same value.
Complexity
Time: O(n log n). The sort takes O(n log n) and the loop is a single O(n) pass, so the sort dominates.
Space: about O(1) extra. The sort works inside the input array and the loop adds one counter. (The sort itself needs a small amount of working memory, which is far below a copy of the array.)
Edge cases and common mistakes
- All values equal.
[4, 4, 4]gives 1: the comparison never finds a change. - All values different.
[3, 1, 2]gives 3. - One element. The loop does not run and the answer is 1.
- Empty array. The code as written returns 1, because the counter starts at 1 without checking the length. The lesson's inputs always have elements, but a version meant for general use would return 0 first when
nums.length == 0. - Counting without sorting. Comparing only neighbours in an unsorted array gives the wrong answer. For
[1, 2, 1]every neighbour differs, giving 3, but there are only 2 distinct values. - Starting the counter at 0. The first element would never be counted, and every answer would be one short.
- Looping from 0.
nums(i - 1)would read index -1 and throw an exception.
Testing
[1, 5, -3, 1, -4, 2, -4, 7, 7]gives6.[4, 4, 4]gives1.[3, 1, 2]gives3.[1, 2, 1]gives2.
