目录
🍁题目要求
🍁示例分析
🍁提示
准备工作:
🍃1.从示例一的输入输出,我们很容易就想到了前缀和,通过一个 sum 变量就能知道什么时候需要做删除操作。
🍃2.删除链表总和为 0 的结点,头结点也存在被删除的可能,所以我们需要一个辅助结点,来帮助我们进行删除操作。
分析解法:
示例1 提供的两种输出结果,其实间接的告诉了我们此题至少两种做法。
示例1 的输出结果如果是 [3,1],我们可以联想到哈希表。
🍃1.从上图上,我们发现 sum 在加加的过程中,如果存在总和为 0 的结点,那么中间一定有两次 sum 的值相同,交叉出现相同的值。可能出现多对相同的值,但是我们只关心最前面那对值(例如上图 3 也重复出现了,但是 0 比 3 先出现,并且在将两个 0 之间的值删除后,3 就只剩下一个了)。
🍃2.既然需要删除,又有相同的值,而哈希表恰好具备这些特点,当 key 值相同的时候,就会产生覆盖的效果,而这种效果如果放在链表中,那么刚好可以做到删除!!
🍃3.于是,我们的思路是:将 ListNode.val 作为哈希表的 键(key),将 ListNode 作为哈希表的 值(val)。我们在添加 key 值的时候,就可以将 sum 添加进去;取值的时候,我们依旧按照链表的值加和的顺序来取,假设我们此时取出一个值为 0 ,此时的 0 就已经不再是 0x99 这个结点了,而是 0x33 这个结点了。(结合上图理解)
代码示例
- class Solution {
- public ListNode removeZeroSumSublists(ListNode head) {
- HashMap
map = new HashMap<>(); - ListNode newHead = new ListNode(0);
- newHead.next = head;
- int sum = 0;
- ListNode cur = newHead;
- while(cur != null) {
- sum += cur.val;
- // map在添加元素的时候,key值相同,结点就会覆盖,就意味着前一个 key 一直加到后一个 key 的结果为 0
- // 这也是为什么 node.val 作 key 的关键
- map.put(sum,cur);
- cur = cur.next;
- }
- sum = 0;
- cur = newHead;
- while(cur != null) {
- sum += cur.val;
- // 当后面的 key 值出现与前面的相等时,通过改变指针指向就可以删去中间的结点
- cur.next = map.get(sum).next;
- cur = cur.next;
- }
- return newHead.next;
- }
- }
复杂度分析
时间复杂度:O(N),遍历两次链表,空间换取时间。
空间复杂度:O(N)
示例2的输出结果如果是 [1,2,1],那么我们就可以使用 "双指针法"。
输出这个结果,无非就是让 -3 和 3 相加变为 0 。此处多加一组示例 [1,2,3,-3,1] 便于后续对比理解。
此处的 "双指针法",并不是两两指针一起走,而是分别控制内外两层循环。并且此处的删除条件就仅仅是 sum = 0,不再是出现相同的和。
🍃1.我们申请一个结点 prev 指向 newHead(不指向 head, 是因为如果出现 sum = 0 的时候,prev 和 cur 刚好指向同一个结点,那就没法进行删除了)。
🍃2.申请一个结点 cur 指向 head,不断往后计算链表的前缀和。
cur 在每一轮循环计算前缀和的时候,会存在两种情况:
- cur 在遍历链表计算前缀和的过程中遇上了 sum = 0,可能在链表末尾,也可能在链表中间
- cur 在遍历链表计算前缀和的过程中没有遇上 sum = 0,但是不代表链表中没有和为 0 的结点。所以控制外层循环的指针需要往后走一步,cur 内层循环进行下一次判定。(不断缩小 cur 遍历链表的范围)
画图理解上述两种情况:
情况一
情况二:
代码示例
- public ListNode removeZeroSumSublists1(ListNode head) {
- ListNode newHead = new ListNode(0);
- newHead.next = head;
- ListNode prev = newHead;
- while(prev != null) {
- ListNode cur = prev.next;
- int sum = 0;
- while(cur != null) {
- sum += cur.val;
- // 链表 val 加和为 0 的时候,说明此时 prev 后面的数据,
- // cur.next 之前的数据都需要删除
- if(sum == 0) {
- prev.next = cur.next;
- break;
- }
- cur = cur.next;
- }
- // 1.如果内存循环正常走到链表末尾,则进行下一轮循环
- // 2.如果内存循环结束的时候,cur 还没走到连链表末尾,说明此时在中途遇到 和为0 了
- // 进行删除操作,并且这轮循环继续向下执行
- if(cur == null) {
- prev = prev.next;
- }
- }
- return newHead.next;
- }
复杂度分析
时间复杂度:O(N^2),prev 每移动一次,cur 遍历一次链表。
空间复杂度:O(1)
本篇博客就到这里了,谢谢观看!