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
and2
fromnums
appear consecutively in the linked list, forming one connected component - Value
4
appears alone (not consecutive with othernums
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
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:
- Skip all nodes whose values are not in
nums
- When we find a node in
nums
, increment our component count (we've found a component start) - Continue through all consecutive nodes that are in
nums
(these belong to the same component) - 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:
-
Convert
nums
to a set:s = set(nums)
allowsO(1)
membership checking instead ofO(n)
with a list. -
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 - ifhead
is notNone
, 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.
-
-
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 EvaluatorExample 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 ins
? No,0
is ins
, 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 nexthead = 1
, in set? Yes, move to nexthead = 2
, in set? No, stop
- Now
head
points to node2
Second iteration:
- First inner while: Skip nodes not in
s
head = 2
, not in set? Yes, move to nexthead = 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 nexthead = 4
, in set? No, stop
- Now
head
points to node4
Third iteration:
- First inner while: Skip nodes not in
s
head = 4
, not in set? Yes, move to nexthead = 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 nexthead = None
(end of list)
- Now
head
isNone
Final Result: ans = 3
The three connected components are:
[0, 1]
- consecutive nodes with values from nums[3]
- single node with value from nums[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 takesO(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 takingO(1)
spacehead
: a pointer to traverse the linked list takingO(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)
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
Linked List Cycle Given a linked list with potentially a loop determine whether the linked list from the first node contains a cycle in it For bonus points do this with constant space Parameters nodes The first node of a linked list with potentially a loop Result Whether there is a loop contained
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 assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!