Facebook Pixel

817. Linked List Components

Problem Description

You have a linked list with unique integer values and an array nums that contains some values from the linked list.

A connected component is formed when values from nums appear consecutively in the linked list. Your task is to count how many such connected components exist.

For example, if the linked list is 1 -> 2 -> 3 -> 4 and nums = [1, 2, 4]:

  • Values 1 and 2 from nums appear consecutively in the linked list, forming one connected component
  • Value 4 appears alone (not consecutive with other nums values), forming another connected component
  • Total: 2 connected components

The key points:

  • Values must be from the nums array to be considered
  • Values must appear consecutively in the linked list to be in the same component
  • Each isolated group of consecutive values from nums counts as one component
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

To count connected components, we need to identify when a new component starts. A new component begins when we encounter a value from nums that wasn't preceded by another value from nums.

Think of walking through the linked list: when we see a value that belongs to nums after seeing values that don't belong to nums (or at the very beginning), we've found the start of a new component. Once we're in a component, we stay in it as long as we keep seeing consecutive values from nums.

The key insight is that we only need to count the starting points of components. We don't need to track the entire component - just recognize when one begins.

This leads to a pattern:

  1. Skip all nodes whose values are not in nums
  2. When we find a node in nums, increment our component count (we've found a component start)
  3. Continue through all consecutive nodes that are in nums (these belong to the same component)
  4. Repeat until we've traversed the entire list

By converting nums to a set first, we can check membership in O(1) time, making our traversal efficient. The solution essentially counts the number of transitions from "not in nums" to "in nums" as we traverse the linked list.

Learn more about Linked List patterns.

Solution Approach

The implementation uses a two-pointer-like traversal with a set data structure for efficient lookups:

  1. Convert nums to a set: s = set(nums) allows O(1) membership checking instead of O(n) with a list.

  2. Main traversal loop: While we haven't reached the end of the linked list:

    • Skip non-component nodes: The first inner while loop while head and head.val not in s moves the pointer past all nodes whose values are not in the set. This positions us either at a node that starts a component or at the end of the list.

    • Count component start: ans += head is not None increments the counter only if we found a node (not the end). This is clever - if head is not None, it means we've found the start of a new component, so we add 1 to our answer.

    • Traverse the component: The second inner while loop while head and head.val in s moves through all consecutive nodes that belong to the current component. We don't need to do anything here except move forward.

  3. Return the count: After traversing the entire list, ans contains the total number of connected components.

The algorithm works in a single pass through the linked list with O(n) time complexity where n is the length of the linked list. The space complexity is O(m) where m is the length of the nums array (for the set).

The pattern here is essentially "count the number of groups" - we identify group boundaries by detecting transitions from non-group elements to group 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 concrete example to illustrate the solution approach.

Example: Linked list: 0 -> 1 -> 2 -> 3 -> 4 -> 5, nums = [0, 1, 3, 5]

Step 1: Convert nums to set

  • s = {0, 1, 3, 5} for O(1) lookups

Step 2: Traverse the linked list

Initial state: head points to node 0, ans = 0

First iteration:

  • First inner while: Check if 0 is not in s? No, 0 is in s, so we don't skip
  • Increment counter: ans = 1 (we found a component start!)
  • Second inner while: Move through consecutive nodes in s
    • head = 0, in set? Yes, move to next
    • head = 1, in set? Yes, move to next
    • head = 2, in set? No, stop
  • Now head points to node 2

Second iteration:

  • First inner while: Skip nodes not in s
    • head = 2, not in set? Yes, move to next
    • head = 3, not in set? No (3 is in set), stop
  • Increment counter: ans = 2 (found another component start!)
  • Second inner while: Move through consecutive nodes in s
    • head = 3, in set? Yes, move to next
    • head = 4, in set? No, stop
  • Now head points to node 4

Third iteration:

  • First inner while: Skip nodes not in s
    • head = 4, not in set? Yes, move to next
    • head = 5, not in set? No (5 is in set), stop
  • Increment counter: ans = 3 (found another component start!)
  • Second inner while: Move through consecutive nodes in s
    • head = 5, in set? Yes, move to next
    • head = None (end of list)
  • Now head is None

Final Result: ans = 3

The three connected components are:

  1. [0, 1] - consecutive nodes with values from nums
  2. [3] - single node with value from nums
  3. [5] - single node with value from nums

The algorithm correctly identified each transition from "not in nums" to "in nums" as a new component start, giving us the total count of 3 connected components.

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, List
8
9class Solution:
10    def numComponents(self, head: Optional[ListNode], nums: List[int]) -> int:
11        """
12        Count the number of connected components in a linked list.
13        A connected component is a sequence of consecutive nodes whose values are all in nums.
14      
15        Args:
16            head: The head of the linked list
17            nums: List of values that form components
18          
19        Returns:
20            Number of connected components
21        """
22        component_count = 0
23        # Convert nums to set for O(1) lookup
24        nums_set = set(nums)
25      
26        current = head
27      
28        while current:
29            # Skip nodes that are not part of any component
30            while current and current.val not in nums_set:
31                current = current.next
32          
33            # If we found a node in nums_set, we've found a new component
34            if current is not None:
35                component_count += 1
36          
37            # Traverse through all consecutive nodes in the current component
38            while current and current.val in nums_set:
39                current = current.next
40      
41        return component_count
42
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    public int numComponents(ListNode head, int[] nums) {
13        // Initialize counter for connected components
14        int componentCount = 0;
15      
16        // Create a HashSet for O(1) lookup of values in nums array
17        Set<Integer> numSet = new HashSet<>();
18        for (int value : nums) {
19            numSet.add(value);
20        }
21      
22        // Traverse the linked list
23        while (head != null) {
24            // Skip nodes that are not in the numSet
25            while (head != null && !numSet.contains(head.val)) {
26                head = head.next;
27            }
28          
29            // If we found a node in numSet, we found a new component
30            if (head != null) {
31                componentCount++;
32            }
33          
34            // Continue through all consecutive nodes that are in numSet
35            // (they belong to the same component)
36            while (head != null && numSet.contains(head.val)) {
37                head = head.next;
38            }
39        }
40      
41        return componentCount;
42    }
43}
44
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    int numComponents(ListNode* head, vector<int>& nums) {
14        // Create a hash set from nums array for O(1) lookup
15        unordered_set<int> numSet(nums.begin(), nums.end());
16      
17        // Counter for the number of connected components
18        int componentCount = 0;
19      
20        // Traverse the linked list
21        while (head != nullptr) {
22            // Skip nodes that are not in the nums set
23            while (head != nullptr && numSet.count(head->val) == 0) {
24                head = head->next;
25            }
26          
27            // If we found a node in nums, it starts a new component
28            if (head != nullptr) {
29                componentCount++;
30            }
31          
32            // Continue through all consecutive nodes that are in nums
33            // (they belong to the same component)
34            while (head != nullptr && numSet.count(head->val) > 0) {
35                head = head->next;
36            }
37        }
38      
39        return componentCount;
40    }
41};
42
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 * Counts the number of connected components in a linked list
15 * where components are defined by consecutive nodes whose values exist in nums array
16 * 
17 * @param head - The head of the linked list
18 * @param nums - Array of values that form the components
19 * @returns The number of connected components
20 */
21function numComponents(head: ListNode | null, nums: number[]): number {
22    // Create a set for O(1) lookup of values in nums
23    const numSet: Set<number> = new Set<number>(nums);
24  
25    // Counter for the number of connected components
26    let componentCount: number = 0;
27  
28    // Pointer to traverse the linked list
29    let currentNode: ListNode | null = head;
30  
31    // Flag to track if we're currently inside a component
32    let isInComponent: boolean = false;
33  
34    // Traverse through the entire linked list
35    while (currentNode !== null) {
36        // Check if current node's value exists in the set
37        if (numSet.has(currentNode.val)) {
38            // If we're entering a new component, increment the counter
39            if (!isInComponent) {
40                isInComponent = true;
41                componentCount++;
42            }
43            // If already in a component, continue without incrementing
44        } else {
45            // Current node is not in the set, so we've exited any component
46            isInComponent = false;
47        }
48      
49        // Move to the next node
50        currentNode = currentNode.next;
51    }
52  
53    return componentCount;
54}
55

Time and Space Complexity

Time Complexity: O(n) where n is the number of nodes in the linked list.

The algorithm traverses the linked list exactly once. In the main while loop, each node is visited at most once through the two inner while loops:

  • The first inner while loop skips nodes that are not in the set s
  • The second inner while loop traverses through consecutive nodes that are in the set s (forming a component)
  • The in operation for checking membership in a set takes O(1) average time

Since each node is processed exactly once and set lookup is O(1), the overall time complexity is O(n).

Space Complexity: O(m) where m is the length of the nums array.

The algorithm creates a set s from the nums list, which requires O(m) space to store all the unique values. The only other variables used are:

  • ans: a counter variable taking O(1) space
  • head: a pointer to traverse the linked list taking O(1) space

Therefore, the total space complexity is dominated by the set storage, resulting in O(m) space complexity.

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

Common Pitfalls

1. Incorrectly Counting Components by Checking Every Node

A common mistake is to increment the component counter for every node that exists in nums, rather than only at the start of each component:

Incorrect approach:

def numComponents(self, head: Optional[ListNode], nums: List[int]) -> int:
    component_count = 0
    nums_set = set(nums)
    current = head
  
    while current:
        if current.val in nums_set:
            component_count += 1  # Wrong! Counts every node, not components
        current = current.next
  
    return component_count

This would return 3 for the example 1 -> 2 -> 3 -> 4 with nums = [1, 2, 4] instead of the correct answer 2.

Solution: Only increment the counter when transitioning from a non-component node to a component node (or at the start if beginning with a component node).

2. Using a Flag Variable Instead of Structural Flow

Another pitfall is overcomplicating the logic with boolean flags to track whether we're inside a component:

Overly complex approach:

def numComponents(self, head: Optional[ListNode], nums: List[int]) -> int:
    component_count = 0
    nums_set = set(nums)
    current = head
    in_component = False
  
    while current:
        if current.val in nums_set:
            if not in_component:
                component_count += 1
                in_component = True
        else:
            in_component = False
        current = current.next
  
    return component_count

While this works, it introduces unnecessary state management and makes the code harder to follow.

Solution: Use the structured approach with two inner while loops - one to skip non-component nodes and another to traverse through component nodes. This naturally handles the transitions without extra state variables.

3. Forgetting to Convert nums to a Set

Using the list directly for membership checking:

Inefficient approach:

def numComponents(self, head: Optional[ListNode], nums: List[int]) -> int:
    component_count = 0
    current = head
  
    while current:
        while current and current.val not in nums:  # O(n) lookup each time!
            current = current.next
      
        if current is not None:
            component_count += 1
      
        while current and current.val in nums:  # O(n) lookup each time!
            current = current.next
  
    return component_count

This changes the time complexity from O(n) to O(n*m) where n is the linked list length and m is the nums array length.

Solution: Always convert nums to a set first for O(1) lookups: nums_set = set(nums)

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