Given the head
of a linked list, we repeatedly delete consecutive sequences of nodes that sum to 0
until there are no such sequences.
After doing so, return the head of the final linked list. You may return any such answer.
(Note that in the examples below, all sequences are serializations of ListNode
objects.)
Example 1:
Input: head = [1,2,-3,3,1] Output: [3,1] Note: The answer [1,2,1] would also be accepted.
Example 2:
Input: head = [1,2,3,-3,4] Output: [1,2,4]
Example 3:
Input: head = [1,2,3,-3,-2] Output: [1]
Constraints:
1
and 1000
nodes.-1000 <= node.val <= 1000
.This problem involves removing consecutive sequences of nodes in a linked list that sum to zero. The optimal approach uses a hash table (or dictionary) to efficiently track prefix sums and identify zero-sum subsequences.
Approach:
Prefix Sum: We iterate through the linked list, calculating the prefix sum at each node. The prefix sum at a node is the sum of all node values from the head of the list up to that node.
Hash Table (Dictionary): We use a hash table to store the prefix sum and the corresponding node. The key is the prefix sum, and the value is a pointer to the node where that prefix sum occurs. If a prefix sum is encountered multiple times, we only keep the latest occurrence (overwriting the earlier one). This is because we're only interested in the last node that results in a particular prefix sum.
Zero-Sum Detection: If we encounter a prefix sum s
that already exists in the hash table, it means the sum of the nodes between the node associated with the previous s
and the current node is zero (because the prefix sums are equal).
Node Removal: To remove the zero-sum subsequence, we directly modify the next
pointer of the node associated with the previous s
to point to the current node's next node, effectively skipping the zero-sum subsequence.
Code Explanation (Python):
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeZeroSumSublists(self, head: Optional[ListNode]) -> Optional[ListNode]:
dummy = ListNode(next=head) # Dummy node simplifies handling the head
prefix_sums = {0: dummy} # Initialize with prefix sum 0 at dummy node
current_sum = 0
curr = dummy
while curr:
current_sum += curr.val
if current_sum in prefix_sums:
#Zero sum subsequence found
prev_node = prefix_sums[current_sum]
prev_node.next = curr.next #Skip the subsequence
prefix_sums = {0:dummy} #Reset the prefix sum dictionary
current_sum=0
curr = dummy
else:
prefix_sums[current_sum] = curr
curr = curr.next
return dummy.next
Time Complexity: O(N), where N is the number of nodes in the linked list. We iterate through the list twice in the worst case.
Space Complexity: O(N) in the worst case to store the prefix sums in the hash table. In the best case (no zero-sum subsequences), it's O(N).
Other Languages:
The solutions provided in Java, C++, Go, Typescript and Rust follow the same logic, only adapting the syntax and data structures specific to each language. The core idea of using prefix sums and a hash table remains consistent. The primary difference is how hash maps are implemented in each language (e.g., HashMap
in Java, unordered_map
in C++, map
in Go).
Example Walkthrough:
Let's trace the Python code with head = [1, 2, -3, 3, 1]
:
prefix_sums = {0: dummy}
(dummy node)1
: current_sum = 1
, prefix_sums = {0: dummy, 1: node1}
3
: current_sum = 3
, prefix_sums = {0: dummy, 1: node1, 3: node2}
0
: current_sum = 0
, 0
is in prefix_sums
, so we remove [1, 2, -3]
. prefix_sums
is reset, and the iteration restarts.3
: current_sum = 3
, prefix_sums = {0: dummy, 3: node3}
4
: current_sum = 4
, prefix_sums = {0: dummy, 3: node3, 4: node4}
[3, 1]
.This approach efficiently solves the problem by avoiding repeated scans of the list and directly modifying pointers to remove zero-sum subsequences.