LeetCode-从链表中删去总和值为零的连续结点

目录

1.题目描述

 2.解题思路

2.1 思路一:[前缀和 + 哈希表]

2.2 思路二:[前缀和+"双指针法"]


1.题目描述

🍁题目要求

 🍁示例分析

 🍁提示

 2.解题思路

准备工作: 

🍃1.从示例一的输入输出,我们很容易就想到了前缀和,通过一个 sum 变量就能知道什么时候需要做删除操作。

🍃2.删除链表总和为 0 的结点,头结点也存在被删除的可能,所以我们需要一个辅助结点,来帮助我们进行删除操作。

分析解法:

示例1 提供的两种输出结果,其实间接的告诉了我们此题至少两种做法。

2.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<Integer,ListNode> 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.2 思路二:[前缀和+"双指针法"]

示例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)


本篇博客就到这里了,谢谢观看!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Master_hl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值