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

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.

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.

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

Which algorithm should you use to find a node that is close to the root of the tree?


Recommended Readings

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

Load More