Problem overview
The task is to build a small data structure, Solution, that holds a collection of integers, initially empty, and supports three operations: Insert(val) adds one copy of a number, Remove(val) removes one copy, and Count(val) returns how many copies are currently present. Duplicates are allowed, so this is a multiset.
In the example, the calls are Insert(5), Insert(2), Insert(5), Count(5), Remove(5), Count(5). After the first three calls the collection holds two 5s and one 2, so the first count is 2. After the removal one 5 is left, so the second count is 1.
Every operation must run in O(1) time, and the structure may use O(n) space for n stored numbers.
Approach: store counts, not elements
A slice of numbers would make Count and Remove scan the whole slice, which is O(n). The order of the numbers does not matter here, only how many copies of each one exist. So store exactly that: a map from each number to its count. Map reads, writes and increments take O(1) on average, and the map holds one entry per distinct number, which fits the space limit.
Go helps in a lucky way: reading a key that is not in a map returns the zero value, which is 0 for an int. A number that was never inserted therefore counts as 0 copies with no special case, and incrementing a missing key creates it with the value 1.
Here is the example as the map sees it. It starts empty. Insert(5) makes it {5: 1}, Insert(2) makes it {5: 1, 2: 1}, and the second Insert(5) makes it {5: 2, 2: 1}. Count(5) reads 2. Remove(5) sees a count of 2, which is positive, and lowers it to 1, so the last Count(5) reads 1. No operation ever looks at more than one key, which is why none of them depends on how many numbers are stored.
Step by step
- Add the
freqfield to the struct, of typemap[int]int. The providedConstructorcreates the map withmake, so the field only has to be declared. - Write
Countas a single read ofs.freq[val]. Missing numbers report 0. - Write
Insertas an increment ofs.freq[val]. The first insert of a number turns the implicit 0 into 1. - Guard
Removewith a check that the stored count is positive. Without it, removing a number that is not present would store -1, and a laterInsertwould bring it to 0 instead of 1. - Decrement inside the guard to record the removal.
The solution in Go
type Solution struct {
freq map[int]int
}
func Constructor() Solution {
return Solution{freq: make(map[int]int)}
}
func (s *Solution) Count(val int) int {
return s.freq[val]
}
func (s *Solution) Insert(val int) {
s.freq[val]++
}
func (s *Solution) Remove(val int) {
if s.freq[val] > 0 {
s.freq[val]--
}
}
The Go details:
- Structs and methods instead of classes. Go has no classes. Data lives in a struct, and methods are functions with a receiver written before the name, here
(s *Solution). The receiver is a pointer so that methods can change the struct, and the map inside is shared anyway because a map value is a reference to its table. - The constructor is a plain function.
Constructor()returns aSolutionwhosefreqwas built withmake. A zeroSolution{}has a nil map:Countwould still work, butInsertwould panic with "assignment to entry in nil map". s.freq[val]++on a map entry. It reads the entry (0 if absent), adds one and writes it back. In Go,++and--are statements, so they cannot be used as values.- Reading does not insert.
Counton a missing number leaves the map unchanged.
Complexity
Time: O(1) per operation on average. Each method does at most one map read and one map write.
Space: O(n). The map has one entry per distinct number ever inserted, which is at most n.
Edge cases and common mistakes
- Removing a number that is absent. The guard makes it a no-op. Without the guard the count would become -1.
- Removing more times than inserted. After
Insert(5)and two calls toRemove(5), the count is 0, not -1. - Counting an unknown number.
Count(9)on an empty collection returns 0. - Zero counts stay in the map. Removing the last copy of a number leaves the key with the value 0 instead of deleting it. That is harmless for
Count, and the entry count still stays within the number of distinct values inserted. Callingdelete(s.freq, val)when the count reaches 0 would keep the map smaller, but the lesson does not need it. - Using the zero value of the struct. Always create the collection with
Constructor(), so the map exists before the firstInsert.
Testing
- The example calls give counts
2then1. Remove(9)followed byCount(9)on an empty collection gives0.Insert(5), thenRemove(5)twice, thenCount(5)gives0.