Facebook Pixel

3063. Linked List Frequency 🔒

EasyHash TableLinked ListCounting
Leetcode Link

Problem Description

You are given the head of a linked list that contains k distinct elements (where elements may appear multiple times). Your task is to return a new linked list of length k where each node contains the frequency count of a distinct element from the original linked list.

The problem asks you to:

  1. Count how many times each distinct element appears in the input linked list
  2. Create a new linked list where each node's value is one of these frequency counts
  3. The output linked list should have exactly k nodes (one for each distinct element)
  4. The order of frequencies in the output doesn't matter

For example:

  • If the input is [1,1,2,1,2,3], there are 3 distinct elements:
    • Element 1 appears 3 times
    • Element 2 appears 2 times
    • Element 3 appears 1 time
  • The output would be [3,2,1] (the frequencies of each distinct element)

The solution uses a hash table (Counter) to track the frequency of each element value while traversing the input linked list. Then it creates a new linked list by iterating through the frequency values and constructing nodes with those frequencies as values. The use of dummy.next = ListNode(val, dummy.next) creates the new linked list in reverse order of iteration, but since order doesn't matter according to the problem, this is acceptable.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The core insight is that we need to transform the linked list from containing element values to containing frequency counts. This naturally suggests a two-step process: first count, then build.

When we need to count occurrences of elements, a hash table (or dictionary) is the go-to data structure because it allows us to efficiently track and update counts as we traverse the list. Each time we encounter an element, we can increment its count in O(1) time.

The key observation is that we don't need to preserve any relationship between the original elements and their frequencies in the output - we only need the frequency values themselves. This means after counting, we can simply extract all the frequency values from our hash table and build a new linked list with these values.

For building the output linked list, we can use a dummy node technique. The clever part of the solution is using dummy.next = ListNode(val, dummy.next) which prepends each new node to the front of the list we're building. This is more efficient than appending to the end (which would require keeping track of the tail), and since the problem states that any order is acceptable, we can take advantage of this flexibility.

The approach is straightforward: traverse once to count (O(n) time), then build a new list with k frequency values (O(k) time). Since k ≤ n, the overall time complexity is O(n), which is optimal as we must examine every node at least once.

Learn more about Linked List patterns.

Solution Approach

The solution uses a Hash Table approach to solve this problem efficiently.

Step 1: Count Frequencies

We initialize a Counter object (which is a specialized dictionary in Python) to track the frequency of each element:

  • cnt = Counter() creates an empty counter
  • We traverse the linked list with a while loop: while head:
  • For each node, we increment the count: cnt[head.val] += 1
  • Move to the next node: head = head.next

After this traversal, cnt contains key-value pairs where keys are the distinct element values and values are their frequencies.

Step 2: Build the Result Linked List

We use the dummy node pattern to construct the output:

  • dummy = ListNode() creates a dummy head node to simplify list construction
  • We iterate through the frequency values: for val in cnt.values()
  • For each frequency value, we create a new node and prepend it to the existing list: dummy.next = ListNode(val, dummy.next)
    • ListNode(val, dummy.next) creates a new node with value val and its next pointer pointing to the current first node
    • Setting dummy.next to this new node effectively adds it to the front of the list

Step 3: Return the Result

We return dummy.next, which skips the dummy node and gives us the actual head of the frequency list.

The key insight in the construction is that ListNode(val, dummy.next) allows us to prepend nodes efficiently. Each new node points to what was previously the first node, and then becomes the new first node. This builds the list in reverse order of iteration through the frequencies, but since the problem allows any order, this approach is both correct and efficient.

Time Complexity: O(n) where n is the number of nodes in the input list
Space Complexity: O(k) where k is the number of distinct elements

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example with the input linked list: [5, 5, 3, 5, 3, 7]

Step 1: Count Frequencies

Starting with an empty Counter, we traverse the linked list:

  • Node 1: value = 5, cnt = {5: 1}
  • Node 2: value = 5, cnt = {5: 2}
  • Node 3: value = 3, cnt = {5: 2, 3: 1}
  • Node 4: value = 5, cnt = {5: 3, 3: 1}
  • Node 5: value = 3, cnt = {5: 3, 3: 2}
  • Node 6: value = 7, cnt = {5: 3, 3: 2, 7: 1}

We have 3 distinct elements with frequencies: 5 appears 3 times, 3 appears 2 times, 7 appears 1 time.

Step 2: Build the Result Linked List

We create a dummy node and iterate through the frequency values [3, 2, 1]:

Initial state: dummy -> None

  • First iteration (val = 3):

    • Create ListNode(3, dummy.next) where dummy.next is None
    • New node: 3 -> None
    • Set dummy.next to this new node
    • Result: dummy -> 3 -> None
  • Second iteration (val = 2):

    • Create ListNode(2, dummy.next) where dummy.next is the node with value 3
    • New node: 2 -> 3 -> None
    • Set dummy.next to this new node
    • Result: dummy -> 2 -> 3 -> None
  • Third iteration (val = 1):

    • Create ListNode(1, dummy.next) where dummy.next is the node with value 2
    • New node: 1 -> 2 -> 3 -> None
    • Set dummy.next to this new node
    • Result: dummy -> 1 -> 2 -> 3 -> None

Step 3: Return Result

Return dummy.next, which gives us the head of the list: [1, 2, 3]

This represents the frequencies of the distinct elements in the original list. The order [1, 2, 3] is different from how we might naturally think about it (3, 2, 1), but since the problem states any order is acceptable, this reverse construction through prepending is both correct and efficient.

Solution Implementation

1# Definition for singly-linked list.
2# class ListNode:
3#     def __init__(self, val=0, next=None):
4#         self.val = val
5#         self.next = next
6
7from typing import Optional
8from collections import Counter
9
10class Solution:
11    def frequenciesOfElements(self, head: Optional[ListNode]) -> Optional[ListNode]:
12        """
13        Counts the frequency of each element in the linked list and returns
14        a new linked list containing these frequencies.
15      
16        Args:
17            head: The head of the input linked list
18          
19        Returns:
20            Head of a new linked list containing frequencies of elements
21        """
22        # Count frequency of each value in the linked list
23        frequency_counter = Counter()
24        current = head
25        while current:
26            frequency_counter[current.val] += 1
27            current = current.next
28      
29        # Create a dummy node to simplify list construction
30        dummy_head = ListNode()
31      
32        # Build the result linked list with frequencies
33        # Each new node is inserted at the beginning (after dummy)
34        for frequency in frequency_counter.values():
35            new_node = ListNode(frequency, dummy_head.next)
36            dummy_head.next = new_node
37      
38        # Return the actual head (skip dummy node)
39        return dummy_head.next
40
1/**
2 * Definition for singly-linked list.
3 * public class ListNode {
4 *     int val;
5 *     ListNode next;
6 *     ListNode() {}
7 *     ListNode(int val) { this.val = val; }
8 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
9 * }
10 */
11class Solution {
12    /**
13     * Creates a new linked list containing the frequencies of elements from the input list.
14     * @param head The head of the input linked list
15     * @return The head of a new linked list containing frequency values
16     */
17    public ListNode frequenciesOfElements(ListNode head) {
18        // HashMap to store the frequency count of each element
19        Map<Integer, Integer> frequencyMap = new HashMap<>();
20      
21        // Traverse the linked list and count frequency of each element
22        ListNode current = head;
23        while (current != null) {
24            // Increment the count for current value, or set to 1 if not present
25            frequencyMap.merge(current.val, 1, Integer::sum);
26            current = current.next;
27        }
28      
29        // Create a dummy node to simplify list construction
30        ListNode dummyHead = new ListNode();
31      
32        // Build the result linked list with frequency values
33        // Each new node is inserted at the beginning (after dummy)
34        for (int frequency : frequencyMap.values()) {
35            ListNode newNode = new ListNode(frequency, dummyHead.next);
36            dummyHead.next = newNode;
37        }
38      
39        // Return the actual head of the result list (skip dummy node)
40        return dummyHead.next;
41    }
42}
43
1/**
2 * Definition for singly-linked list.
3 * struct ListNode {
4 *     int val;
5 *     ListNode *next;
6 *     ListNode() : val(0), next(nullptr) {}
7 *     ListNode(int x) : val(x), next(nullptr) {}
8 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
9 * };
10 */
11class Solution {
12public:
13    /**
14     * Creates a new linked list containing the frequencies of each element in the input list.
15     * @param head - pointer to the head of the input linked list
16     * @return pointer to the head of a new linked list containing frequencies
17     */
18    ListNode* frequenciesOfElements(ListNode* head) {
19        // Hash map to store the frequency count of each element
20        unordered_map<int, int> frequencyMap;
21      
22        // Traverse the linked list and count occurrences of each value
23        ListNode* current = head;
24        while (current != nullptr) {
25            frequencyMap[current->val]++;
26            current = current->next;
27        }
28      
29        // Create a dummy node to simplify list construction
30        ListNode* dummyHead = new ListNode();
31      
32        // Build the result list by inserting frequencies at the beginning
33        // Note: This creates the list in reverse order of map iteration
34        for (const auto& [value, frequency] : frequencyMap) {
35            // Create new node with frequency as value and insert at the beginning
36            ListNode* newNode = new ListNode(frequency, dummyHead->next);
37            dummyHead->next = newNode;
38        }
39      
40        // Return the actual head (skip dummy node)
41        return dummyHead->next;
42    }
43};
44
1/**
2 * Definition for singly-linked list.
3 * class ListNode {
4 *     val: number
5 *     next: ListNode | null
6 *     constructor(val?: number, next?: ListNode | null) {
7 *         this.val = (val===undefined ? 0 : val)
8 *         this.next = (next===undefined ? null : next)
9 *     }
10 * }
11 */
12
13/**
14 * Creates a new linked list containing the frequencies of elements from the input linked list
15 * @param head - The head of the input linked list
16 * @returns A new linked list where each node contains the frequency count of elements from the original list
17 */
18function frequenciesOfElements(head: ListNode | null): ListNode | null {
19    // Map to store the frequency count of each element value
20    const frequencyMap: Map<number, number> = new Map<number, number>();
21  
22    // Traverse the linked list and count frequencies
23    let currentNode: ListNode | null = head;
24    while (currentNode !== null) {
25        const currentValue: number = currentNode.val;
26        const currentFrequency: number = frequencyMap.get(currentValue) || 0;
27        frequencyMap.set(currentValue, currentFrequency + 1);
28        currentNode = currentNode.next;
29    }
30  
31    // Create a dummy node to simplify list construction
32    const dummyHead: ListNode = new ListNode();
33  
34    // Build the result linked list with frequency values
35    // Insert each frequency at the beginning of the list
36    for (const frequency of frequencyMap.values()) {
37        const newNode: ListNode = new ListNode(frequency, dummyHead.next);
38        dummyHead.next = newNode;
39    }
40  
41    // Return the actual head of the result list (skip dummy node)
42    return dummyHead.next;
43}
44

Time and Space Complexity

Time Complexity: O(n), where n is the length of the linked list.

  • The first while loop traverses the entire linked list once to count frequencies: O(n)
  • Building the Counter dictionary involves updating counts for each node: O(n) total operations
  • The for loop iterates through the unique values in the Counter (at most n unique values): O(n) in worst case
  • Each insertion operation in the for loop (creating a new ListNode) is O(1)
  • Overall time complexity: O(n) + O(n) = O(n)

Space Complexity: O(n), where n is the length of the linked list.

  • The Counter dictionary stores at most n key-value pairs (when all elements are unique): O(n)
  • The output linked list will have at most n nodes (when all elements are unique): O(n)
  • Additional variables like dummy and head pointer: O(1)
  • Overall space complexity: O(n) + O(n) = O(n)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Modifying the Original Linked List

A common mistake is accidentally modifying the input linked list while traversing it. For example:

Incorrect approach:

def frequenciesOfElements(self, head: Optional[ListNode]) -> Optional[ListNode]:
    frequency_counter = Counter()
    # DON'T DO THIS - modifies the original list
    while head:
        frequency_counter[head.val] += 1
        head = head.next  # This changes the original head reference
  
    # Later code might assume head still points to the start
    # but head is now None!

Solution: Always use a separate pointer for traversal:

current = head  # Create a copy of the reference
while current:
    frequency_counter[current.val] += 1
    current = current.next  # Move the copy, not the original

2. Forgetting to Handle Empty Input

The code handles empty lists correctly due to Python's Counter and the dummy node pattern, but explicitly checking can make the code more readable and prevent edge case issues:

Enhanced solution:

def frequenciesOfElements(self, head: Optional[ListNode]) -> Optional[ListNode]:
    if not head:  # Explicit empty list check
        return None
  
    # Rest of the implementation...

3. Confusion About List Construction Order

The line dummy_head.next = ListNode(frequency, dummy_head.next) builds the list in reverse order of iteration. While this doesn't matter for this problem, developers might expect the output order to match the iteration order:

If order matters (alternative approach):

# To maintain iteration order, keep track of the tail
dummy_head = ListNode()
tail = dummy_head

for frequency in frequency_counter.values():
    tail.next = ListNode(frequency)
    tail = tail.next

return dummy_head.next

4. Not Importing Required Modules

Forgetting to import Counter from collections will cause a NameError:

Fix: Always ensure proper imports:

from collections import Counter  # Don't forget this import!

5. Assuming Node Values are Hashable

The solution assumes that head.val can be used as a dictionary key. If node values were unhashable types (like lists), this would fail. While this is unlikely in typical LeetCode problems, it's worth noting for real-world applications.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Consider the classic dynamic programming of longest increasing subsequence:

Find the length of the longest subsequence of a given sequence such that all elements of the subsequence are sorted in increasing order.

For example, the length of LIS for [50, 3, 10, 7, 40, 80] is 4 and LIS is [3, 7, 40, 80].

What is the recurrence relation?


Recommended Readings

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

Load More