3092. Most Frequent IDs
Problem Description
You are given two integer arrays nums
and freq
, both of length n
. These arrays work together to describe a series of operations on a collection of IDs.
For each index i
from 0
to n-1
:
nums[i]
represents an ID valuefreq[i]
represents how many times that ID should be added or removed:- If
freq[i] > 0
: Addfreq[i]
copies of IDnums[i]
to the collection - If
freq[i] < 0
: Remove-freq[i]
copies of IDnums[i]
from the collection
- If
Your task is to track the frequency of the most common ID after each operation. Return an array ans
of length n
where ans[i]
represents the count of the most frequent ID in the collection after completing the operation at index i
.
If the collection becomes empty at any point, the answer for that step should be 0
.
For example, if you have:
nums = [2, 3, 2, 1]
freq = [3, 2, -1, 1]
The operations would be:
- Add 3 copies of ID 2 → Collection has {2: 3}, most frequent count is 3
- Add 2 copies of ID 3 → Collection has {2: 3, 3: 2}, most frequent count is 3
- Remove 1 copy of ID 2 → Collection has {2: 2, 3: 2}, most frequent count is 2
- Add 1 copy of ID 1 → Collection has {2: 2, 3: 2, 1: 1}, most frequent count is 2
The answer would be [3, 3, 2, 2]
.
Intuition
The key challenge is efficiently tracking the maximum frequency after each operation. A naive approach would be to scan through all ID frequencies after each update, but this would be too slow for large inputs.
We need a data structure that can quickly give us the maximum value - a max heap (priority queue) is perfect for this. However, there's a complication: when we update an ID's frequency, its old frequency value might still be sitting in the heap.
For example, if ID 2 has frequency 5 and we add 3 more, its new frequency is 8. But the heap now contains both 5 and 8 for the same ID. We can't efficiently remove the outdated 5 from the middle of the heap.
The clever insight is to use a "lazy deletion" strategy. Instead of removing outdated values immediately, we:
- Keep track of how many times each frequency value is outdated using a
lazy
counter - When a frequency changes from
old_freq
tonew_freq
, we markold_freq
as needing deletion by incrementinglazy[old_freq]
- Add the
new_freq
to the heap regardless
When we need the maximum frequency, we peek at the top of the heap. If that value has pending deletions (i.e., lazy[top] > 0
), we know it's outdated, so we pop it and decrement its lazy counter. We repeat this until we find a valid maximum or the heap is empty.
This approach works because:
- We only care about the maximum value at each step
- Outdated values that aren't at the top don't affect our answer
- We clean up outdated values lazily, only when they bubble up to the top
- Each value is added once and removed once, maintaining efficiency
The cnt
hash table tracks current frequencies of each ID, while the heap combined with lazy deletion gives us quick access to the maximum frequency at any point.
Learn more about Heap (Priority Queue) patterns.
Solution Approach
The solution uses a combination of hash tables and a max heap with lazy deletion to efficiently track the maximum frequency after each operation.
Data Structures Used:
cnt
: A Counter (hash table) that stores the current frequency of each IDlazy
: A Counter that tracks how many times each frequency value needs to be deleted from the heappq
: A min heap (negated values to simulate max heap) that stores all frequency valuesans
: The result array
Step-by-Step Implementation:
-
Initialize data structures:
cnt = Counter() # ID -> current frequency lazy = Counter() # frequency value -> deletion count ans = [] # result array pq = [] # priority queue (max heap)
-
Process each operation
(x, f)
:a. Mark old frequency for lazy deletion:
lazy[cnt[x]] += 1
Before updating, we mark the current frequency of ID
x
as outdated by incrementing its count inlazy
.b. Update the frequency:
cnt[x] += f
Add
f
to the frequency of IDx
(can be positive or negative).c. Add new frequency to heap:
heappush(pq, -cnt[x])
Push the new frequency to the heap (negated for max heap behavior).
d. Clean up outdated values at the top:
while pq and lazy[-pq[0]] > 0: lazy[-pq[0]] -= 1 heappop(pq)
While the top of the heap has pending deletions, remove it and decrement its lazy counter.
e. Record the maximum frequency:
ans.append(0 if not pq else -pq[0])
If the heap is empty, the maximum frequency is 0; otherwise, it's the negated top value.
Why This Works:
- The heap always contains all frequency values that have ever existed
- The
lazy
counter tells us which values are outdated - We only clean up outdated values when they reach the top of the heap
- This ensures O(log n) time for heap operations while avoiding expensive deletions from the middle of the heap
Time Complexity: O(n log n) where n is the length of the input arrays Space Complexity: O(n) for the hash tables and heap
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's trace through a small example to understand how the solution works:
Input: nums = [1, 2, 1, 2]
, freq = [2, 3, -1, 1]
Initial State:
cnt = {}
(empty)lazy = {}
(empty)pq = []
(empty heap)ans = []
(result array)
Operation 1: Add 2 copies of ID 1
lazy[cnt[1]]
→lazy[0] = 1
(mark old frequency 0 for deletion)cnt[1] = 0 + 2 = 2
(update frequency)- Push
-2
to heap →pq = [-2]
- Clean top: Check
lazy[2] = 0
(no deletion needed) - Append
2
to answer →ans = [2]
Operation 2: Add 3 copies of ID 2
lazy[cnt[2]]
→lazy[0] = 2
(mark old frequency 0 for deletion)cnt[2] = 0 + 3 = 3
(update frequency)- Push
-3
to heap →pq = [-3, -2]
- Clean top: Check
lazy[3] = 0
(no deletion needed) - Append
3
to answer →ans = [2, 3]
Operation 3: Remove 1 copy of ID 1
lazy[cnt[1]]
→lazy[2] = 1
(mark old frequency 2 for deletion)cnt[1] = 2 + (-1) = 1
(update frequency)- Push
-1
to heap →pq = [-3, -2, -1]
- Clean top: Check
lazy[3] = 0
(no deletion needed) - Append
3
to answer →ans = [2, 3, 3]
Operation 4: Add 1 copy of ID 2
lazy[cnt[2]]
→lazy[3] = 1
(mark old frequency 3 for deletion)cnt[2] = 3 + 1 = 4
(update frequency)- Push
-4
to heap →pq = [-4, -3, -2, -1]
- Clean top: Check
lazy[4] = 0
(no deletion needed) - Append
4
to answer →ans = [2, 3, 3, 4]
Final Result: [2, 3, 3, 4]
Notice how:
- The heap contains all frequency values ever created:
[-4, -3, -2, -1]
- The
lazy
counter tracks outdated values:{0: 2, 2: 1, 3: 1}
- We only clean outdated values when they reach the top (which didn't happen in this example)
- The current state is: ID 1 has frequency 1, ID 2 has frequency 4
If we continued with an operation that reduced ID 2's frequency, the outdated -4
would eventually reach the top and get lazily deleted.
Solution Implementation
1from typing import List
2from collections import Counter
3import heapq
4
5class Solution:
6 def mostFrequentIDs(self, nums: List[int], freq: List[int]) -> List[int]:
7 # Track the current frequency count for each ID
8 id_frequency = Counter()
9
10 # Track outdated frequency values that should be ignored in the heap
11 # Key: frequency value, Value: count of how many times this outdated value exists
12 outdated_frequencies = Counter()
13
14 # Result list to store the maximum frequency at each step
15 result = []
16
17 # Max heap (using negative values since Python has min heap by default)
18 max_heap = []
19
20 # Process each ID and its frequency change
21 for id_value, frequency_change in zip(nums, freq):
22 # Mark the current frequency of this ID as outdated
23 # (since we're about to update it)
24 outdated_frequencies[id_frequency[id_value]] += 1
25
26 # Update the frequency count for this ID
27 id_frequency[id_value] += frequency_change
28
29 # Add the new frequency to the max heap (negative for max heap behavior)
30 heapq.heappush(max_heap, -id_frequency[id_value])
31
32 # Clean up the heap by removing outdated maximum values from the top
33 while max_heap and outdated_frequencies[-max_heap[0]] > 0:
34 # Decrease the count of this outdated frequency value
35 outdated_frequencies[-max_heap[0]] -= 1
36 # Remove the outdated value from heap
37 heapq.heappop(max_heap)
38
39 # Add the current maximum frequency to the result
40 # If heap is empty, the maximum frequency is 0
41 result.append(0 if not max_heap else -max_heap[0])
42
43 return result
44
1class Solution {
2 public long[] mostFrequentIDs(int[] nums, int[] freq) {
3 // Map to store current frequency count for each ID
4 Map<Integer, Long> idToFrequency = new HashMap<>();
5
6 // Map to track outdated frequencies in the priority queue
7 // Key: frequency value, Value: count of how many times this outdated frequency appears
8 Map<Long, Integer> outdatedFrequencies = new HashMap<>();
9
10 int n = nums.length;
11 long[] result = new long[n];
12
13 // Max heap to track the highest frequency
14 PriorityQueue<Long> maxHeap = new PriorityQueue<>(Collections.reverseOrder());
15
16 for (int i = 0; i < n; i++) {
17 int currentId = nums[i];
18 int frequencyChange = freq[i];
19
20 // Mark the old frequency of this ID as outdated (if it exists)
21 Long oldFrequency = idToFrequency.getOrDefault(currentId, 0L);
22 outdatedFrequencies.merge(oldFrequency, 1, Integer::sum);
23
24 // Update the frequency count for the current ID
25 idToFrequency.merge(currentId, (long) frequencyChange, Long::sum);
26
27 // Add the new frequency to the max heap
28 Long newFrequency = idToFrequency.get(currentId);
29 maxHeap.add(newFrequency);
30
31 // Clean up outdated frequencies from the top of the heap
32 while (!maxHeap.isEmpty() &&
33 outdatedFrequencies.getOrDefault(maxHeap.peek(), 0) > 0) {
34 // Remove one occurrence of this outdated frequency
35 outdatedFrequencies.merge(maxHeap.poll(), -1, Integer::sum);
36 }
37
38 // The current maximum frequency (or 0 if heap is empty)
39 result[i] = maxHeap.isEmpty() ? 0 : maxHeap.peek();
40 }
41
42 return result;
43 }
44}
45
1class Solution {
2public:
3 vector<long long> mostFrequentIDs(vector<int>& nums, vector<int>& freq) {
4 // Map to store the current frequency count for each ID
5 unordered_map<int, long long> idFrequencyMap;
6
7 // Map to track outdated frequency values that need to be removed from the heap
8 // Key: frequency value, Value: count of how many times this outdated value exists
9 unordered_map<long long, int> outdatedFrequencies;
10
11 int n = nums.size();
12 vector<long long> result(n);
13
14 // Max heap to maintain the maximum frequency
15 priority_queue<long long> maxHeap;
16
17 for (int i = 0; i < n; ++i) {
18 int currentId = nums[i];
19 int frequencyChange = freq[i];
20
21 // Mark the old frequency of currentId as outdated
22 outdatedFrequencies[idFrequencyMap[currentId]]++;
23
24 // Update the frequency count for currentId
25 idFrequencyMap[currentId] += frequencyChange;
26
27 // Push the new frequency to the max heap
28 maxHeap.push(idFrequencyMap[currentId]);
29
30 // Clean up outdated values from the top of the heap
31 while (!maxHeap.empty() && outdatedFrequencies[maxHeap.top()] > 0) {
32 outdatedFrequencies[maxHeap.top()]--;
33 maxHeap.pop();
34 }
35
36 // Store the maximum frequency at position i
37 result[i] = maxHeap.empty() ? 0 : maxHeap.top();
38 }
39
40 return result;
41 }
42};
43
1function mostFrequentIDs(nums: number[], freq: number[]): number[] {
2 // Map to store the current frequency count for each ID
3 const idFrequencyMap: Map<number, number> = new Map();
4
5 // Map to track outdated frequency values that need to be removed from the heap
6 // Key: frequency value, Value: count of how many times this outdated value exists
7 const outdatedFrequencies: Map<number, number> = new Map();
8
9 const n: number = nums.length;
10 const result: number[] = new Array(n);
11
12 // Max heap to maintain the maximum frequency (using array with custom comparison)
13 const maxHeap: number[] = [];
14
15 // Helper function to maintain max heap property when pushing
16 const heapPush = (value: number): void => {
17 maxHeap.push(value);
18 maxHeap.sort((a, b) => b - a);
19 };
20
21 // Helper function to pop from max heap
22 const heapPop = (): number | undefined => {
23 return maxHeap.shift();
24 };
25
26 for (let i = 0; i < n; i++) {
27 const currentId: number = nums[i];
28 const frequencyChange: number = freq[i];
29
30 // Get current frequency of the ID (default to 0 if not exists)
31 const oldFrequency: number = idFrequencyMap.get(currentId) || 0;
32
33 // Mark the old frequency of currentId as outdated
34 outdatedFrequencies.set(oldFrequency, (outdatedFrequencies.get(oldFrequency) || 0) + 1);
35
36 // Update the frequency count for currentId
37 const newFrequency: number = oldFrequency + frequencyChange;
38 idFrequencyMap.set(currentId, newFrequency);
39
40 // Push the new frequency to the max heap
41 heapPush(newFrequency);
42
43 // Clean up outdated values from the top of the heap
44 while (maxHeap.length > 0 && (outdatedFrequencies.get(maxHeap[0]) || 0) > 0) {
45 const topValue: number = maxHeap[0];
46 outdatedFrequencies.set(topValue, outdatedFrequencies.get(topValue)! - 1);
47 heapPop();
48 }
49
50 // Store the maximum frequency at position i
51 result[i] = maxHeap.length === 0 ? 0 : maxHeap[0];
52 }
53
54 return result;
55}
56
Time and Space Complexity
Time Complexity: O(n × log n)
The algorithm iterates through the input arrays once, performing n
iterations where n
is the length of the arrays. In each iteration:
- Counter updates (
cnt[x]
andlazy
operations) takeO(1)
time heappush
operation takesO(log k)
time wherek
is the heap size- The while loop performs
heappop
operations, each takingO(log k)
time
The key insight is that although there's a while loop inside the main loop, each frequency value can be pushed to the heap and popped at most once throughout the entire execution. Since we perform at most n
push operations and at most n
pop operations total across all iterations, the amortized time complexity is O(n × log n)
.
Space Complexity: O(n)
The space usage comes from:
cnt
Counter: stores at mostn
unique IDs, soO(n)
spacelazy
Counter: tracks outdated heap values, at mostO(n)
entriespq
heap: can contain at mostn
elements in the worst caseans
list: stores exactlyn
results
All data structures use linear space with respect to the input size n
, resulting in a total space complexity of O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting to Handle Negative Frequencies Resulting in Zero Count
The Pitfall:
When freq[i]
is negative and reduces an ID's count to zero or below, developers often forget to properly handle this edge case. If an ID's frequency becomes 0, it should be treated as if it doesn't exist in the collection, but the heap might still contain its old positive frequency values.
Problematic Code:
# Incorrect: Not handling zero or negative frequencies properly id_frequency[id_value] += frequency_change if id_frequency[id_value] > 0: heapq.heappush(max_heap, -id_frequency[id_value])
The Issue: This approach fails to add zero frequencies to the heap, which means the lazy deletion mechanism won't work correctly. The heap needs ALL frequency transitions to properly track what values are outdated.
Correct Solution:
# Always update and push to heap, even for zero or negative values outdated_frequencies[id_frequency[id_value]] += 1 id_frequency[id_value] += frequency_change heapq.heappush(max_heap, -id_frequency[id_value])
2. Incorrect Lazy Deletion Timing
The Pitfall: Marking the old frequency as outdated AFTER updating the frequency count, which causes the wrong value to be marked for deletion.
Problematic Code:
# Incorrect order: updating frequency before marking old value as outdated id_frequency[id_value] += frequency_change outdated_frequencies[id_frequency[id_value]] += 1 # Wrong! This marks the NEW frequency heapq.heappush(max_heap, -id_frequency[id_value])
The Issue: This marks the NEW frequency as outdated instead of the OLD one, leading to incorrect maximum frequency calculations.
Correct Solution:
# Mark old frequency BEFORE updating outdated_frequencies[id_frequency[id_value]] += 1 # Marks OLD frequency id_frequency[id_value] += frequency_change # Now update heapq.heappush(max_heap, -id_frequency[id_value]) # Push NEW frequency
3. Not Handling Empty Collection Properly
The Pitfall: Assuming the heap always has valid values without checking if all IDs have been removed from the collection.
Problematic Code:
# Incorrect: Always returning the heap top without checking if collection is empty result.append(-max_heap[0]) # Crashes if heap is empty!
Correct Solution:
# Check if heap is empty after cleanup result.append(0 if not max_heap else -max_heap[0])
4. Using Regular Heap Instead of Handling Negative Values
The Pitfall: Python's heapq is a min heap, so directly using it without negation will give the minimum frequency instead of maximum.
Problematic Code:
# Incorrect: Using min heap directly heapq.heappush(max_heap, id_frequency[id_value]) # This creates a MIN heap result.append(max_heap[0] if max_heap else 0) # Returns MINIMUM frequency
Correct Solution:
# Negate values for max heap behavior heapq.heappush(max_heap, -id_frequency[id_value]) # Negative for max heap result.append(-max_heap[0] if max_heap else 0) # Negate back when retrieving
Which algorithm should you use to find a node that is close to the root of the tree?
Recommended Readings
https assets algo monster cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue is a data structure
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!