Problem overview
Given an IntArray named nums, distinctValues 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. The values 1, -4 and 7 each appear twice but are counted once.
Approach
The brute-force idea is to compare every element with every earlier one and count it only if it is new. That takes O(n^2) time. A hash set would make it linear, but it needs extra memory, and the lesson asks for O(n log n) time with O(1) extra space.
Sorting gives a cheap test for "have I seen this before?". After sorting, equal values sit next to each other, so a value is new exactly when it differs from the element just before it. One comparison with the neighbour replaces the comparison with everything seen earlier.
Sorted, the example becomes [-4, -4, -3, 1, 1, 2, 5, 7, 7]. Counting the places where a value differs from its left neighbour gives -3, 1, 2, 5 and 7, which is 5 changes. Add the first element, which is always new, and the total is 6.
Step by step
- Sort in place.
nums.sort()rearranges the array itself. Usingsorted()would build a new list and leavenumsin its original order, which defeats the purpose. - Start the counter at 1.
distinctis avarbecause it changes. The first element is always a new value, so the count begins at 1. - Loop from the second element.
for (i in 1 until nums.size)visits indices1tonums.size - 1. Starting at 1 meansnums[i - 1]always exists. - Compare with the previous element. If
nums[i] != nums[i - 1], a new run of equal values starts here, so incrementdistinct. - Return the count.
The solution in Kotlin
class Solution {
fun distinctValues(nums: IntArray): Int {
nums.sort()
var distinct = 1
for (i in 1 until nums.size) {
if (nums[i] != nums[i - 1]) {
distinct++
}
}
return distinct
}
}
Kotlin details worth knowing:
1 until nums.sizeis a range that excludes its end. The similar-looking1..nums.sizeincludes the end, so it would readnums[nums.size]and throw anArrayIndexOutOfBoundsException.- When the range is empty, as with a one-element array (
1 until 1), the loop body never runs and the answer stays 1. !=onIntcompares values. It is the opposite of==.distinct++is a statement here. Kotlin doesn't need a semicolon at the end of a line.- The function sorts the caller's array. After the call, the array passed in is in ascending order.
Complexity
- Time:
O(n log n)for the sort andO(n)for the single pass. The sort dominates. - Space:
O(1)extra, since the array is sorted in place and only two variables are added.
Edge cases and common mistakes
- Empty array. This version returns 1, because it assumes a first element exists. On an empty
IntArraythe loop doesn't run, so the starting value 1 comes back even though the true answer is 0. The lesson's inputs have at least one value; if yours might not, return 0 first whennums.isEmpty(). - Skipping the sort. Without it,
[1, 2, 1]would count 3, since each element differs from its neighbour. - Starting the counter at 0. The first element would then never be counted, giving a result one too small.
- Starting the loop at 0.
nums[-1]throws anArrayIndexOutOfBoundsException. - All equal.
[4, 4, 4]never triggers theif, so the answer is 1.
Testing
[1, 5, -3, 1, -4, 2, -4, 7, 7]returns6.[4, 4, 4]returns1.[3, 1, 2]returns3.[9]returns1.