Facebook Pixel

3092. Most Frequent IDs

MediumArrayHash TableOrdered SetHeap (Priority Queue)
Leetcode Link

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 value nums[i] are added to the collection at step i.
  • Removal of IDs: If freq[i] is negative, -freq[i] IDs with the value nums[i] are removed from the collection at step i.

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:

  1. 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).

  2. 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.

  3. 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 to freq.
  • 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 append 0).

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:

  1. 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.
  2. Algorithm:

    • Iterate Over Every Operation: For each pair of ID and frequency from nums and freq:
      • Update lazy: Increment the count of the current frequency of ID in lazy to mark this frequency for potential removal.
      • Update Frequency in cnt: Adjust the count of ID in cnt 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.
      • 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.
  3. 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 and freq 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 Evaluator

Example 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:

  1. 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.
  2. 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 of pq), so ans = [3].
    • 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 of cnt[1]), hence ans = [3, 3].
    • Step 2 (nums[2] = 1, freq[2] = -1):

      • Update lazy: lazy[cnt[1]] = 1 (cnt[1] is now outdated, frequency decreased from 3 to 2).
      • Reduce the frequency of ID 1: cnt[1] = 2.
      • Add -2 to the priority queue: pq = [-3, -2, -2].
      • Perform lazy deletion: Remove -3 from pq because it is outdated (as indicated by lazy), decrement lazy[-3].
      • Max frequency is 2, hence ans = [3, 3, 2].
    • 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, hence ans = [3, 3, 2, 2].

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.


Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:
Question 1 out of 10

Which of the following is a min heap?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!


Load More