Given a singly linked list, remove all consecutive sequences of nodes that sum up to zero. You must repeatedly remove zero-sum consecutive sequences until no such sequence exists in the list. The final linked list should be returned.
head
of a singly linked list.
At first glance, the problem seems to require checking every possible consecutive subsequence of the linked list to see if it sums to zero, and then removing those nodes. A brute-force approach would be to try all start and end positions, sum the values, and remove nodes as needed. However, this is inefficient and can be slow for large lists.
To optimize, we can use the concept of prefix sums. If two nodes have the same prefix sum, then the nodes between them sum to zero. This is similar to finding subarrays with zero sum in an array, but applied to a linked list. By storing prefix sums and their corresponding nodes in a hash map, we can efficiently find and remove zero-sum subsequences in a single pass.
The key insight is that after removing a zero-sum sequence, we may create new zero-sum sequences that overlap or are adjacent, so we need to repeat the process until no such sequence remains. But with the prefix sum approach, we can do this in one or two passes efficiently.
We use a two-pass algorithm based on prefix sums and a hash map:
next
pointer to the node after the last occurrence of the current prefix sum (from the hash map).
We use a hash map for O(1) lookups and a dummy node to simplify edge cases. This approach ensures all zero-sum consecutive sequences are removed in O(n) time.
Consider the input list: 1 -> 2 -> -3 -> 3 -> 1
The prefix sum 0 is seen at the dummy and after -3. This means the nodes between them (1, 2, -3) sum to zero.
3 -> 1
.
3 -> 1
.
The optimized approach is much faster and preferable for large lists.
The key to solving the "Remove Zero Sum Consecutive Nodes from Linked List" problem efficiently is to leverage prefix sums and a hash map. By mapping prefix sums to the most recent node where they occur, we can identify and remove zero-sum consecutive sequences in a single or double pass, resulting in an O(n) solution. This approach is both elegant and efficient, making use of linked list manipulation and hash maps for optimal performance.
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def removeZeroSumSublists(self, head: ListNode) -> ListNode:
dummy = ListNode(0)
dummy.next = head
prefix_sum = 0
prefix_map = {}
node = dummy
# First pass: record the last occurrence of each prefix sum
while node:
prefix_sum += node.val
prefix_map[prefix_sum] = node
node = node.next
# Second pass: remove zero-sum sequences
prefix_sum = 0
node = dummy
while node:
prefix_sum += node.val
node.next = prefix_map[prefix_sum].next
node = node.next
return dummy.next
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeZeroSumSublists(ListNode* head) {
ListNode* dummy = new ListNode(0);
dummy->next = head;
unordered_map<int, ListNode*> prefix_map;
int prefix_sum = 0;
ListNode* node = dummy;
// First pass: record the last occurrence of each prefix sum
while (node) {
prefix_sum += node->val;
prefix_map[prefix_sum] = node;
node = node->next;
}
// Second pass: remove zero-sum sequences
prefix_sum = 0;
node = dummy;
while (node) {
prefix_sum += node->val;
node->next = prefix_map[prefix_sum]->next;
node = node->next;
}
ListNode* result = dummy->next;
delete dummy;
return result;
}
};
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
import java.util.*;
class Solution {
public ListNode removeZeroSumSublists(ListNode head) {
ListNode dummy = new ListNode(0);
dummy.next = head;
Map<Integer, ListNode> prefixMap = new HashMap<>();
int prefixSum = 0;
ListNode node = dummy;
// First pass: record the last occurrence of each prefix sum
while (node != null) {
prefixSum += node.val;
prefixMap.put(prefixSum, node);
node = node.next;
}
// Second pass: remove zero-sum sequences
prefixSum = 0;
node = dummy;
while (node != null) {
prefixSum += node.val;
node.next = prefixMap.get(prefixSum).next;
node = node.next;
}
return dummy.next;
}
}
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var removeZeroSumSublists = function(head) {
let dummy = new ListNode(0);
dummy.next = head;
let prefixMap = new Map();
let prefixSum = 0;
let node = dummy;
// First pass: record the last occurrence of each prefix sum
while (node) {
prefixSum += node.val;
prefixMap.set(prefixSum, node);
node = node.next;
}
// Second pass: remove zero-sum sequences
prefixSum = 0;
node = dummy;
while (node) {
prefixSum += node.val;
node.next = prefixMap.get(prefixSum).next;
node = node.next;
}
return dummy.next;
};