题目
标题和出处
标题:从链表中删去总和值为零的连续结点
难度
6 级
题目描述
要求
给你一个链表的头结点 head \texttt{head} head,请你编写代码,反复删去链表中由总和值为 0 \texttt{0} 0 的连续结点组成的序列,直到不存在这样的序列为止。
删除完毕后,请你返回最终结果链表的头结点。你可以返回任何满足题目要求的答案。
(注意,下面示例中的所有序列,都是对 ListNode \texttt{ListNode} ListNode 对象序列化的表示。)
示例
示例 1:
输入:
head
=
[1,2,-3,3,1]
\texttt{head = [1,2,-3,3,1]}
head = [1,2,-3,3,1]
输出:
[3,1]
\texttt{[3,1]}
[3,1]
提示:答案
[1,2,1]
\texttt{[1,2,1]}
[1,2,1] 也是正确的。
示例 2:
输入:
head
=
[1,2,3,-3,4]
\texttt{head = [1,2,3,-3,4]}
head = [1,2,3,-3,4]
输出:
[1,2,4]
\texttt{[1,2,4]}
[1,2,4]
示例 3:
输入:
head
=
[1,2,3,-3,-2]
\texttt{head = [1,2,3,-3,-2]}
head = [1,2,3,-3,-2]
输出:
[1]
\texttt{[1]}
[1]
数据范围
- 给你的链表中可能有 1 \texttt{1} 1 到 1000 \texttt{1000} 1000 个结点
- 对于链表中的每个结点,结点的值满足 -1000 ≤ node.val ≤ 1000 \texttt{-1000} \le \texttt{node.val} \le \texttt{1000} -1000≤node.val≤1000
解法
思路和算法
这道题要求从链表中删去总和值为零的连续结点。为了得到总和值为零的连续结点片段,需要对每个结点计算从链表的头结点到该结点的所有结点值的总和。定义一个结点的「结点总和」为从链表的头结点到该结点的所有结点值的总和。
由于需要删除链表中的结点,且头结点可能被删除,因此需要创建哑结点 dummyHead \textit{dummyHead} dummyHead,令 dummyHead . val = 0 \textit{dummyHead}.\textit{val} = 0 dummyHead.val=0, dummyHead . next = head \textit{dummyHead}.\textit{next} = \textit{head} dummyHead.next=head。
假设 x x x 和 y y y 是链表中的两个结点,从 x x x 开始向后移动一次或多次可以到达 y y y,且 x x x 和 y y y 的结点总和相等,则从结点 x . next x.\textit{next} x.next 到 y y y 的所有结点值的和为零,可以将这些结点全部删除,删除这些结点的方法是令 x . next x.\textit{next} x.next 指向 y . next y.\textit{next} y.next,执行该操作之后, x x x 的下一个结点为原始链表中 y y y 的下一个结点,因此达到删除从结点 x . next x.\textit{next} x.next 到 y y y 的所有结点的效果。
为了得到每个结点的结点总和,需要遍历链表,并使用哈希表记录每个结点总和对应的结点。从哑结点开始,对于每个结点,将结点值加到结点总和(哑结点的结点值为 0 0 0),然后将结点总和与当前结点存入哈希表。遍历结束之后,哈希表中存储了所有出现过的结点总和,以及每个结点总和对应的最后一个结点。
得到所有出现过的结点总和与对应的最后一个结点之后,从哑结点开始再次遍历链表并重新计算每个结点的结点总和。当遍历到结点 x x x 时,如果结点总和是 sum \textit{sum} sum,则从哈希表中得到结点总和 sum \textit{sum} sum 对应的结点 y y y,令 x . next x.\textit{next} x.next 指向 y . next y.\textit{next} y.next。可能有两种情况:
-
如果 x x x 和 y y y 是同一个结点,则该操作不会改变链表结构;
-
如果 x x x 和 y y y 是不同的结点,则该操作删除从结点 x . next x.\textit{next} x.next 到 y y y 的所有结点,被删除的结点的结点值的和为零。
遍历结束之后,链表中剩下的结点一定不存在总和值为零的连续结点,理由如下:假设遍历到结点 x x x 时,对应的结点总和为 sum \textit{sum} sum,结点 y y y 是满足结点总和为 sum \textit{sum} sum 的最后一个结点,则令 x . next x.\textit{next} x.next 指向 y . next y.\textit{next} y.next,在该操作之后,链表中只剩下结点 x x x 满足结点总和为 sum \textit{sum} sum,因此只有一个结点满足结点总和为 sum \textit{sum} sum,同理可知,遍历结束之后,每个结点总和的结点都是唯一的,不存在两个结点对应的结点总和相等,因此也不存在总和值为零的连续结点。
遍历结束之后,返回 dummyHead . next \textit{dummyHead}.\textit{next} dummyHead.next。
代码
class Solution {
public ListNode removeZeroSumSublists(ListNode head) {
ListNode dummyHead = new ListNode(0, head);
Map<Integer, ListNode> map = new HashMap<Integer, ListNode>();
int sum = 0;
ListNode node = dummyHead;
while (node != null) {
sum += node.val;
map.put(sum, node);
node = node.next;
}
sum = 0;
node = dummyHead;
while (node != null) {
sum += node.val;
node.next = map.get(sum).next;
node = node.next;
}
return dummyHead.next;
}
}
复杂度分析
-
时间复杂度: O ( n ) O(n) O(n),其中 n n n 是链表的长度。需要遍历链表两次,每次哈希表操作的时间都是 O ( 1 ) O(1) O(1),因此总时间复杂度是 O ( n ) O(n) O(n)。
-
空间复杂度: O ( n ) O(n) O(n),其中 n n n 是链表的长度。需要使用哈希表记录每个结点总和对应的最后一个结点,不同的结点总和的数量不会超过链表中的结点数量。