从链表中删去总和值为零的连续节点

标签: 哈希表 链表

难度: Medium

给你一个链表的头节点 head,请你编写代码,反复删去链表中由 总和 值为 0 的连续节点组成的序列,直到不存在这样的序列为止。

删除完毕后,请你返回最终结果链表的头节点。

你可以返回任何满足题目要求的答案。

(注意,下面示例中的所有序列,都是对 ListNode 对象序列化的表示。)

示例 1:

输入:head = [1,2,-3,3,1]
输出:[3,1]
提示:答案 [1,2,1] 也是正确的。

示例 2:

输入:head = [1,2,3,-3,4]
输出:[1,2,4]

示例 3:

输入:head = [1,2,3,-3,-2]
输出:[1]

提示:

  • 给你的链表中可能有 1 到 1000 个节点。
  • 对于链表中的每个节点,节点的值:-1000 <= node.val <= 1000.

Submission

运行时间: 48 ms

内存: 15.4 MB

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
from collections import defaultdict

class Solution:
    def removeZeroSumSublists(self, head: ListNode) -> ListNode:
        pre_sum = defaultdict(ListNode)
        dummy = ListNode(next=head)
        cur = dummy
        s = 0
        while cur:
            s += cur.val
            pre_sum[s] = cur
            cur = cur.next
        s = 0
        cur = dummy
        while cur:
            s += cur.val
            cur.next = pre_sum[s].next
            cur = cur.next
        return dummy.next

Explain

此题解采用前缀和和哈希表的方法来解决问题。首先,创建一个虚拟头节点 dummy,该节点的下一个节点是给定链表的头节点 head。使用一个字典 pre_sum 来存储到当前节点为止的前缀和以及对应的最后一个节点。遍历链表两次,第一次遍历计算每个节点的前缀和,并在 pre_sum 中存储该前缀和最后一次出现的节点。第二次遍历使用前缀和来直接跳过总和为零的子链表,通过将当前节点的 next 指针指向 pre_sum 中存储的前缀和对应的节点的 next。这样,所有总和为零的子链表都被删除了。

时间复杂度: O(n)

空间复杂度: O(n)

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
from collections import defaultdict

class Solution:
    def removeZeroSumSublists(self, head: ListNode) -> ListNode:
        pre_sum = defaultdict(ListNode)  # 前缀和到节点的映射
        dummy = ListNode(next=head)  # 创建虚拟头节点
        cur = dummy
        s = 0
        while cur:
            s += cur.val  # 计算当前的前缀和
            pre_sum[s] = cur  # 更新前缀和对应的最后一个节点
            cur = cur.next
        s = 0
        cur = dummy
        while cur:
            s += cur.val
            cur.next = pre_sum[s].next  # 跳过总和为零的子链表
            cur = cur.next
        return dummy.next

Explore

在此算法中,前缀和是用来记录从链表开始到当前节点的所有值的累加和。哈希表(在此用作前缀和到节点的映射)用于快速查找是否之前出现过相同的前缀和。如果在链表的某个位置出现了与之前某个位置相同的前缀和,这意味着这两个位置之间的节点总和为零。通过将前一个位置的节点直接连接到当前位置的下一个节点,可以有效地删除总和为零的子链表。这种方法通过映射前缀和到其最后出现的节点,确保了即使有多个相同的前缀和出现,也能通过最后一次出现的位置来删除中间总和为零的部分,从而确保找到并删除所有总和为零的子链表。

在第一次遍历时,维护前缀和对应的最后一个节点是因为我们希望在发现总和为零的子链表时,能够直接跳过这整个子链表。如果我们记录的是第一个遇到的节点,那么当再次遇到相同的前缀和时,我们只能确认从第一个记录的节点到当前节点的子链表总和为零,但这无法帮助我们处理后续可能还有的总和为零的子链表。通过更新为最后一个节点,我们确保了每次遇到相同的前缀和时,都能跳过从上次记录的节点到当前节点之间的全部节点,这样更有效地处理了所有可能的情况。

虚拟头节点 `dummy` 主要用于简化链表操作,特别是在头部节点需要被删除或修改时。通过添加一个虚拟头节点,我们保证了即使原始链表的头节点需要被删除(例如头部的前几个节点构成的子链表总和为零),我们也能轻松地处理这一情况,因为我们总是可以通过 `dummy.next` 来访问更新后的真实头节点。如果没有虚拟头节点,算法仍然可以执行,但需要额外的条件判断来处理头节点的特殊情况,这会使得算法的实现更加复杂和容易出错。