3092. Most Frequent IDs
Problem Description
The task is to manage a collection of IDs that undergoes changes over time based on two input arrays, nums
and freq
, which are of equal length n
. Each element in nums
represents an ID, and the corresponding element in freq
dictates how many IDs are added to or removed from the collection at each step.
- Addition of IDs: If
freq[i]
is positive,freq[i]
IDs with the valuenums[i]
are added to the collection at stepi
. - Removal of IDs: If
freq[i]
is negative,-freq[i]
IDs with the valuenums[i]
are removed from the collection at stepi
.
The goal is to return an array ans
of length n
, where ans[i]
is the count of the most frequent ID in the collection after the i
-th step. If the collection is empty at any step, ans[i]
should be 0 for that step.
Intuition
To solve this problem, we need an efficient way to keep track of the frequency of IDs and determine the most frequent one after each operation. This involves using a combination of data structures:
-
Hash Table (
cnt
): This will track the current frequency of each ID in the collection. For each operation, we update the frequency of the ID accordingly (increase or decrease). -
Lazy Deletion with Hash Table (
lazy
): This auxiliary structure helps manage frequencies that need to be archived. Whenever an ID frequency is updated, it means an old frequency must be decreased since a different frequency becomes relevant. We use this table to delay the deletion of out-of-date frequencies in our max-heap. -
Priority Queue (Max Heap): Maintaining a max-heap of frequencies allows real-time access to the highest current frequency. Since Python's
heapq
is a min-heap by default, we store negative values of frequencies to effectively simulate a max-heap.
During each step:
- Update the
cnt
for the current ID according tofreq
. - Reflect changes in
lazy
to account for past frequencies that are now outdated. - Push the new frequency onto the heap.
- Remove elements from the heap whose frequencies are no longer valid as indicated by
lazy
. - Append the top of the heap (maximum frequency) to
ans
, unless the heap is empty (in which case append0
).
This approach ensures we efficiently track the most frequent ID throughout a series of additions and removals.
Learn more about Heap (Priority Queue) patterns.
Solution Approach
The solution approach leverages a combination of data structures: a hash table, a priority queue, and a lazy deletion mechanic. Here's a step-by-step walkthrough:
-
Data Structures:
- Hash Table (
cnt
): Utilized to store the frequency count of each ID. It is updated for every operation to reflect the current number of each ID in the collection. - Lazy Deletion Hash Table (
lazy
): Used to keep track of obsolete frequencies that need removal from the priority queue. This helps in efficiently maintaining the correct state of the max-heap. - Priority Queue (Max Heap): Implemented with a min-heap by using negative frequencies; this allows quick access to the ID with the maximum count after any operation.
- Hash Table (
-
Algorithm:
- Iterate Over Every Operation: For each pair of
ID
andfrequency
fromnums
andfreq
:- Update
lazy
: Increment the count of the current frequency ofID
inlazy
to mark this frequency for potential removal. - Update Frequency in
cnt
: Adjust the count ofID
incnt
using the current frequency (freq[i]
). - Push New Frequency to Heap: Add the updated negative frequency of
ID
to the priority queue (pq
). - Lazy Deletion in Priority Queue: While the top element in the priority queue is outdated (as indicated by
lazy
), remove it:- Decrement the count in
lazy
for the current top element and pop it from the queue.
- Decrement the count in
- Update Answer Array: Append the current most frequent count (negative of the top of the heap) to the resulting array, or append
0
if the heap is empty.
- Update
- Iterate Over Every Operation: For each pair of
-
Efficiency:
- The lazy deletion helps manage the validity of the top element in the priority queue without requiring the queue to be reconstructed from scratch after each update.
- This ensures the algorithm can efficiently handle a series of operations, maintaining an overall time complexity of approximately (O(n \log n)), where (n) is the length of the
nums
andfreq
arrays.
This solution effectively manages the dynamic nature of the collection, ensuring the current most frequent ID can be retrieved efficiently at each step.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a simple example using the solution approach described above.
Given:
nums = [1, 2, 1, 3]
freq = [3, 2, -1, 1]
Let's break down each step according to the solution approach:
-
Initialize Data Structures:
cnt = {}
: A hash table to store current frequencies of IDs.lazy = {}
: A hash table for lazy deletion.pq = []
: An empty priority queue (implemented with a min-heap using negative values).ans = []
: Result array to store the most frequent count.
-
Step-by-Step Execution:
-
Step 0 (
nums[0] = 1
,freq[0] = 3
):- Update the frequency of ID
1
:cnt[1] = 3
. - Add
-3
to the priority queue:pq = [-3]
. - No elements to remove in lazy deletion.
- Max frequency is
3
(top ofpq
), soans = [3]
.
- Update the frequency of ID
-
Step 1 (
nums[1] = 2
,freq[1] = 2
):- Update the frequency of ID
2
:cnt[2] = 2
. - Add
-2
to the priority queue:pq = [-3, -2]
. - Remove outdated frequencies using lazy, if any.
- Max frequency remains
3
(value ofcnt[1]
), henceans = [3, 3]
.
- Update the frequency of ID
-
Step 2 (
nums[2] = 1
,freq[2] = -1
):- Update lazy:
lazy[cnt[1]] = 1
(cnt[1]
is now outdated, frequency decreased from3
to2
). - Reduce the frequency of ID
1
:cnt[1] = 2
. - Add
-2
to the priority queue:pq = [-3, -2, -2]
. - Perform lazy deletion: Remove
-3
frompq
because it is outdated (as indicated bylazy
), decrementlazy[-3]
. - Max frequency is
2
, henceans = [3, 3, 2]
.
- Update lazy:
-
Step 3 (
nums[3] = 3
,freq[3] = 1
):- Update the frequency of ID
3
:cnt[3] = 1
. - Add
-1
to the priority queue:pq = [-2, -2, -1]
. - No further elements require removal in this step.
- Max frequency is still
2
, henceans = [3, 3, 2, 2]
.
- Update the frequency of ID
-
The final result array ans
represents the most frequent ID count after each step.
Solution Implementation
1from typing import List
2from collections import Counter
3from heapq import heappush, heappop
4
5class Solution:
6 def mostFrequentIDs(self, nums: List[int], freq: List[int]) -> List[int]:
7 # Counter to keep track of the frequencies of each ID
8 counter = Counter()
9 # Lazy counter to manage the frequency of removal from heap
10 lazy = Counter()
11 # List to store the result for each step
12 result = []
13 # A max heap to keep track of the most frequent counts
14 max_heap = []
15
16 # Iterate over each (ID, frequency) pair
17 for id, frequency in zip(nums, freq):
18 # Increment the lazy counter for the current frequency count
19 lazy[counter[id]] += 1
20 # Update the frequency count of the current ID
21 counter[id] += frequency
22 # Push the new negative frequency onto the max heap
23 heappush(max_heap, -counter[id])
24
25 # Adjust the max heap and lazy counter
26 # If the max heap's top element is in the lazy to be ignored, remove it
27 while max_heap and lazy[-max_heap[0]] > 0:
28 lazy[-max_heap[0]] -= 1
29 heappop(max_heap)
30
31 # Append the most frequent ID's frequency to result or 0 if heap is empty
32 result.append(0 if not max_heap else -max_heap[0])
33
34 return result
35
1import java.util.HashMap;
2import java.util.Map;
3import java.util.PriorityQueue;
4import java.util.Collections;
5
6class Solution {
7 public long[] mostFrequentIDs(int[] nums, int[] freq) {
8 // Map to track the frequency count of each number
9 Map<Integer, Long> countMap = new HashMap<>();
10
11 // Map to track the number of invalid counts due to lazy updates in the priority queue
12 Map<Long, Integer> lazyMap = new HashMap<>();
13
14 int n = nums.length;
15 long[] result = new long[n];
16
17 // Priority Queue to keep track of current highest frequency counts in reversed order
18 PriorityQueue<Long> maxHeap = new PriorityQueue<>(Collections.reverseOrder());
19
20 for (int i = 0; i < n; ++i) {
21 int number = nums[i];
22 int frequency = freq[i];
23
24 // Increase the 'lazy' count for the old frequency count of the number
25 lazyMap.merge(countMap.getOrDefault(number, 0L), 1, Integer::sum);
26
27 // Update the frequency count for the number with its current frequency
28 countMap.merge(number, (long) frequency, Long::sum);
29
30 // Add the updated frequency count to the priority queue
31 maxHeap.add(countMap.get(number));
32
33 // Remove elements from the priority queue which are invalidated by lazy updates
34 while (!maxHeap.isEmpty() && lazyMap.getOrDefault(maxHeap.peek(), 0) > 0) {
35 lazyMap.merge(maxHeap.poll(), -1, Integer::sum);
36 }
37
38 // Add the current highest frequency count to the result array
39 result[i] = maxHeap.isEmpty() ? 0 : maxHeap.peek();
40 }
41
42 return result;
43 }
44}
45
1class Solution {
2public:
3 std::vector<long long> mostFrequentIDs(std::vector<int>& nums, std::vector<int>& freq) {
4 // Map to store the cumulative frequency of each number
5 std::unordered_map<int, long long> countMap;
6
7 // Map to lazy evaluate the removal of outdated frequencies
8 std::unordered_map<long long, int> lazyRemovalMap;
9
10 int n = nums.size(); // Number of elements
11 std::vector<long long> result(n); // Result vector to store the answer at each step
12
13 // Max heap (priority queue) to always access the most frequent ID at the top
14 std::priority_queue<long long> maxHeap;
15
16 // Iterate over each element
17 for (int i = 0; i < n; ++i) {
18 int id = nums[i]; // Current ID
19 int frequency = freq[i]; // Frequency to add for the current ID
20
21 // Increment the lazy removal count for the current cumulative frequency of 'id'
22 lazyRemovalMap[countMap[id]]++;
23
24 // Update the cumulative frequency for the current ID
25 countMap[id] += frequency;
26
27 // Push the updated frequency into the max heap
28 maxHeap.push(countMap[id]);
29
30 // Remove outdated frequencies if necessary
31 while (!maxHeap.empty() && lazyRemovalMap[maxHeap.top()] > 0) {
32 lazyRemovalMap[maxHeap.top()]--;
33 maxHeap.pop();
34 }
35
36 // Store the most frequent ID's frequency in the result array
37 result[i] = maxHeap.empty() ? 0 : maxHeap.top();
38 }
39
40 return result; // Return the resultant vector with most frequent values at each step
41 }
42};
43
1// Function to compute the most frequent IDs
2function mostFrequentIDs(nums: number[], freq: number[]): number[] {
3 // Map to store the cumulative frequency of each number
4 const countMap: Map<number, number> = new Map();
5
6 // Map to lazy evaluate the removal of outdated frequencies
7 const lazyRemovalMap: Map<number, number> = new Map();
8
9 const n: number = nums.length; // Number of elements
10 const result: number[] = new Array(n); // Result array to store the answer at each step
11
12 // Max heap to always access the most frequent ID at the top
13 const maxHeap: number[] = [];
14
15 // Helper function to manage the max heap
16 function pushHeap(value: number) {
17 maxHeap.push(value);
18 maxHeap.sort((a, b) => b - a);
19 }
20
21 // Iterate over each element
22 for (let i = 0; i < n; ++i) {
23 const id: number = nums[i]; // Current ID
24 const frequency: number = freq[i]; // Frequency to add for the current ID
25
26 // Increment the lazy removal count for the current cumulative frequency of 'id'
27 const currentCount = countMap.get(id) || 0;
28 lazyRemovalMap.set(currentCount, (lazyRemovalMap.get(currentCount) || 0) + 1);
29
30 // Update the cumulative frequency for the current ID
31 countMap.set(id, currentCount + frequency);
32
33 // Push the updated frequency into the max heap
34 pushHeap(countMap.get(id)!);
35
36 // Remove outdated frequencies if necessary
37 while (maxHeap.length > 0 && (lazyRemovalMap.get(maxHeap[0]) || 0) > 0) {
38 lazyRemovalMap.set(maxHeap[0], lazyRemovalMap.get(maxHeap[0])! - 1);
39 maxHeap.shift();
40 }
41
42 // Store the most frequent ID's frequency in the result array
43 result[i] = maxHeap.length > 0 ? maxHeap[0] : 0;
44 }
45
46 return result; // Return the resultant array with most frequent values at each step
47}
48
Time and Space Complexity
The time complexity of the code is O(n * log n)
. This complexity arises due to the use of a priority queue (heap) to track the most frequent IDs. Every insertion and removal operation from the heap takes O(log n)
time, and since such operations are performed for each element in nums
(where n
is the length of nums
), the overall complexity results in O(n * log n)
.
The space complexity of the code is O(n)
. This is because the code utilizes a Counter
to store frequencies of IDs, a separate lazy
counter for tracking lazy updates, and a priority queue pq
. Each of these data structures can store up to n
elements, leading to a total space usage of O(n)
.
Learn more about how to find time and space complexity quickly.
Which of the following is a min heap?
Recommended Readings
https algomonster s3 us east 2 amazonaws com 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
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 algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!