Problem overview
The method maxValNumOfOccurrences receives an array of integers, nums, and returns an array of two integers: the largest value, then the number of times it appears.
For nums = [2, 7, 11, 8, 11, 8, 3, 11] the largest value is 11, and it appears three times, so the result is [11, 3]. The statement asks for O(n) time and O(1) extra space, and the follow-up asks whether one loop over the array is enough.
Approach: update the maximum and the count together
The simplest plan has two passes: find the maximum with one loop, then count how many elements equal it with a second loop. That is already O(n) time and O(1) space, but it reads the array twice.
func twoPass(_ nums: [Int]) -> [Int] {
var maxValue = nums[0]
for value in nums where value > maxValue {
maxValue = value
}
var count = 0
for value in nums where value == maxValue {
count += 1
}
return [maxValue, count]
}
Here where after a for-in loop filters which elements run the body. For the example array it returns [11, 3] as well.
The single-pass version has one practical advantage: it never needs to look at an element again, so it would also work on values that arrive one at a time and cannot be re-read.
A single pass works because each element tells you everything you need at the moment you see it. Keep two variables: the largest value so far and how many times it has appeared so far. For each element there are three cases:
- It is greater than the current maximum. This is a new champion, seen once so far. Replace the maximum and reset the count to 1. Occurrences of the old, smaller maximum no longer matter.
- It is equal to the current maximum. This is one more occurrence, so add 1 to the count.
- It is smaller. Nothing changes.
On the example, the running state (maxValue, count) goes (2, 1), (7, 1), (11, 1), then stays (11, 1) for 8, becomes (11, 2) on the second 11, stays for 8 and 3, and ends at (11, 3).
Step by step
- Declare
maxValueandcountwithvar. Both are reassigned in the loop.maxValuestarts atnums[0], a value that really is in the array. Starting at 0 would be wrong for an array of negative numbers.countstarts at 0. - Loop with
for value in nums. Swift'sfor-inneeds no parentheses and no index. - Test
value > maxValue. The comparison is strict: an equal value is not a new maximum, just another occurrence. - On a new maximum, assign it and set
count = 1. This is the first occurrence of the new maximum. - Add
else if value == maxValueand incrementcount. Usingelse ifkeeps the two cases exclusive, so a value that just became the maximum is not counted twice. - Return
[maxValue, count]. The order matches what the task asks for.
Why does count start at 0 when the first element is already the maximum? The loop visits nums[0] too. It equals maxValue, so the else if branch counts it as the first occurrence.
The solution in Swift
class Solution {
func maxValNumOfOccurrences(_ nums: [Int]) -> [Int] {
var maxValue = nums[0]
var count = 0
for value in nums {
if value > maxValue {
maxValue = value
count = 1
} else if value == maxValue {
count += 1
}
}
return [maxValue, count]
}
}
Swift details worth knowing:
- No
++. Swift has no increment operator, so the code usescount += 1. - The loop variable is a constant. Inside
for value in nums,valueis a freshleton every iteration. Assigning to it is a compile error, and it is not needed here. - Braces and
else if. The braces are mandatory, and} else ifgoes on the line of the closing brace by convention. - Arrays are values.
numsis a copy-on-assignment value type and the method never changes it, so the caller's array is untouched. - The return type. Returning two numbers as an
[Int]is what the lesson's tests expect, with the maximum at index 0 and the count at index 1.
Calling it:
let solution = Solution()
print(solution.maxValNumOfOccurrences([2, 7, 11, 8, 11, 8, 3, 11]))
print(solution.maxValNumOfOccurrences([-5, -5, -9]))
print(solution.maxValNumOfOccurrences([4]))
[11, 3]
[-5, 2]
[4, 1]
Complexity
Time: O(n). The loop visits each element once and does a constant amount of work for it. Space: O(1). The method keeps two integers and builds a two-element result, whatever the size of nums.
Edge cases and common mistakes
- All values negative.
[-5, -5, -9]returns[-5, 2]. This is the reason for starting fromnums[0]instead of 0. - A single element.
[4]returns[4, 1]. - All elements equal.
[6, 6, 6]returns[6, 3]: the first element is counted by theelse ifbranch, and the other two add 1 each. - The maximum at the end.
[1, 2, 2, 9]returns[9, 1]. The count is reset when 9 arrives, which discards the two 2s. - Forgetting the reset. If a new maximum does not set
count = 1, the count carries over occurrences of an older, smaller value. - Two separate
ifs. Replacingelse ifwith a secondifmakes a new maximum setcount = 1and then immediately match the equality test and add another 1, giving 2. - An empty array.
nums[0]on an empty array stops the program with a run-time crash. The task describes inputs that have at least one element; if empty input were possible, aguard !nums.isEmptycheck at the top would be needed.
Testing
[2, 7, 11, 8, 11, 8, 3, 11]gives[11, 3].[-5, -5, -9]gives[-5, 2].[4]gives[4, 1].[6, 6, 6]gives[6, 3].[1, 2, 2, 9]gives[9, 1].
