【LeetCode刷题笔记】链表

141. 环形链表

解题思路:
  • 1. 哈希表 , 最容易想到的方法是遍历所有节点,每次遍历到一个节点时,判断该节点此前是否被访问过。
  • 2. 快慢指针 「Floyd 判圈算法」(又称龟兔赛跑算法)快慢指针从 head 开始走, 慢指针每次只移动1步 ,而 快指针每次移动2步
  • 如果在移动的过程中, 快指针追上慢指针 ,就说明该链表 存在环 。否则 快指针 将到达链表 尾部 ,该链表 不存在环

注意,这个代码 fast 与 slow 的比较不能写在 while 循环的条件判断中,因为 fast 和 slow 都是从 head 开始的,它们一开始就相等,如果写在 while 循环的条件判断中,可能上来就不会执行 while 循环直接返回了。

142. 环形链表 II

解题思路:
  • 1. 哈希 使用 HashSet 存储访问过的节点,当再次遇到已经在 set 中存在的节点时就是入环节点
解题思路:
  • 2. 快慢指针法 慢指针走1步,快指针走2步 ,如果 快慢指针相遇 ,说明 存在环 ,否则不存在环。
  • 快慢指针相遇后 ,让 快指针从头开始 走, 慢指针 相遇点 开始走,二者 同步移动 ,最后一定会 相遇在入环处
 
这个代码的另一种写法:

876. 链表的中间结点

解题思路:
  • 1.  单指针遍历两次 ,第一次计算 总长度 ,第二次移动到 总长度的一半处 ,即中间节点
  • 2.  快慢指针 ,快指针每次移动2步,慢指针每次移动1步, 快指针 到达 末尾 时, 慢指针 就是 中间节点
  • 整体来看就是 快指针走了整个链表长度 ,而 慢指针走了链表的一半
  • 注意点:判断循环退出条件使用 fast != null && fast.next != null 可以满足节点数不管是 偶数 还是 奇数 ,都能找到 正确的中间节点 。当节点数是 偶数 时, slow 最后落在 中间靠右 的中间节点,当节点数是 奇数 时, slow 最后正好落在 中间节点

160. 相交链表

解题思路:
  • 1. 可以暴力 双重循环 判断 A 中的每一个节点 是否在B 中。
  • 2. 哈希 ,先将 A 中所有结点存到 HashSet 中,然后检查 B 中的每一个结点是否在 HashSet 中出现。
  • 3. 双指针 ,设  a, b 分别指向 A B 的头部,然后向后移动,其中 一个到末尾就换到另一个头部继续 ,直到 a, b 指针相等 为止,这是两个相交链表的特性。(如果 不相交 则二者会同时走到 null
下面整个算法的执行过程示意:

 

扩展:两个有环链表的交点

题目中的两个链表不存在环,如果是两个有环链表,该如何查找它们的交点? 

如果两个链表都有环,则有三种情况:

其中第一种是两个环独立不相交,第二种情况如下:

如果入环节点 loop1 == loop2,则只有第二种情况,此时跟寻找两个无环链表的相交节点代码一样。

第三种情况如下:

对于第一种和第三种情况,只需要从 loop1 开始循环走,如果走的过程中遇到了 loop2 节点,则说明 loop1 和 loop2 都是入环相交节点返回任一个都行,如果走了一圈后发现又回到了 loop1 自身,则说明两个链表不相交,返回 null。

以上代码提交到160题可以完全通过,因为代码覆盖了有环和无环的情况。 

234. 回文链表

解题思路:
  • 快慢指针找中间节点 ,然后 反转后半部分链表 与前半部分对比
  • 注意点:快慢指针找中间节点时,让 快指针先走一步 ,这样链表长度为 偶数 的时候, slow 找到的才是 中间靠左 的节点。

 这个题唯一需要注意的是:要让快指针先走一步,这样总是能够找到中间靠左的中间节点。

对于偶数的情况如下: 

对于奇数的情况如下:  

另外,在比较左右两部分时,退出 while 循环后,两个指针要么同时走到 null(偶数情况),要么左边的走到中间节点,右边走到 null 了(奇数情况)。因此只要这个中间比对过程没有出现不等的情况,最后退出 while 循环时肯定是回文链表,返回 true 即可。

剑指 Offer 22. 链表中倒数第k个节点

解题思路:
  • 快慢指针 ,先让 fast 向后走 k 步, 然后 同步移动快慢指针 fast 走到链尾 空节点 时, slow 刚好指向链表的 倒数第k个 节点。

 

19. 删除链表的倒数第 N 个结点

解题思路:
  • 1. 快慢指针 + 记住 slow 前一个节点 ,可以利用前面一题的代码,快指针先行 n 步,然后快慢指针同步移动直到 fast 遇到 null 退出循环。只不过在这个过程中用一个 prev 指针在 slow 移动之前记住它,这样退出循环时,slow 就是要删除的节点,而 prev slow 的前一个节点,将其指针指向 slow.next,并将 slow.next 断开即可。

这个代码需要注意,有可能退出循环时,prev 是空,这时说明要删除的是头结点,即此时 slow 没有移动停留在 head 处。 

解题思路:
  • 2. 快慢指针 + 虚拟头结点 ,快慢指针都从 dummy 虚拟头结点开始走,
  • 1)先让 fast 指针走 n + 1 步来到第 n + 1 个节点(从 head 算),
  • 2) 然后 同时移动slow和fast 指针,直到 fast = null 时终止,此时 slow 是倒数第 n + 1 个节点, slow.next 就是倒数第 N 个要删除的节点
  • 3)执行 slow.next = slow.next.next 就可以删除倒数第 N 个节点。
  • 返回 dummy.next 即原始头节点。
 

 

因为在链表的头部添加了一个哑结点,slow 和 head 相当于后退了一步,从哑结点开始走,所以  fast 先走的步数要在原来的基础上加 1,这样可以保证 fast 和 slow 之间间隔了 N 个节点,当 fast 到达末尾的空位置时,slow 就正好指向了倒数第 N + 1 个节点,slow 的下一个节点就是要删除的节点了。

另外使用哑结点的好处是不需要关心删除的是头结点的特殊情况处理(如方法1中的),简化了逻辑,这是一种链表题目中常见的防御编程技巧。

注意删除倒数第N个节点和前面一道题的查找倒数第N个节点的重要区别:删除一个节点,我们需要找到被删除节点的前驱节点,而查找则没有这个必要。但是当删除的节点是头结点时,它没有前驱节点,这就不太好办了,此时就是体现哑结点的优势所在了。 

 

注意,更加严谨的写法应该是断开要删除节点的 next 指针,将其置空,如下: 

虽然前面一种的写法在力扣上提交可以通过没有问题,但这并不代表在实际开发中这样做没有问题,在实际开发中强烈建议断开要删除的节点的 next 指针。 

解题思路:
  • 3. 使用 全部压栈 ,然后 弹出前 n 个栈顶 , 此时 取出下一个栈顶 倒数第 n + 1 个 节点,进行删除操作。

这种方法其实跟直接使用数组或List遍历没有区别,试想,我们可以把每个节点都顺序放入一个数组中 ,然后从数组的后面开始数 N 个就找到要删除的节点了。但是这种做法需要用额外 O(N) 的空间,如果题目有空间复杂度限制,就不能使用这种方法了。

206. 反转链表

解题思路:
  • 1. 递归 ,首先反转 head.next 开头的剩余链表,反转结果返回的是新的表头 newHead ,反转后 head.next 是新的表尾 newTail ,然后把旧表头 head 挂到 newTail 的后面,并将旧表头 head.next 置空 ,最后返回 newHead
  • 递归终止:head == null 或 head.next == null 时都返回 head ,无需反转。
可以简化写成这样:
还是建议给 head.next 起一个名字,好理解一点,不然时间久了再看这个代码就会不知道自己为啥要 head.next.next = head 了。

 

解题思路:
  • 2.   迭代 +  双指针 prev 指向 前一个节点 cur 指向 当前节点 ,只要 cur 不为空 ,就不断将 cur.next 指向前一个 prev
  • 但在这之前需要先记住 cur.next 记作 next ,然后将 prev 向后 移动到 cur ,将 cur 向后 移动到 next ,最后返回 prev 节点即可。
 

整体来看,就是 cur 修改完指针指向前一个之后,不断向后移动,而 prev 则不断移动到 cur 的前一个位置(也可以理解为是用 prev 记住 cur 的前一个位置)。

92. 反转链表 II

解题思路:
  • 1. 先找到 [left..right] 前驱节点后继节点,然后将 [left..right] 原始链表断开 ,再 反转 [left..right] ,最后再接回去!
  • 具体地,首先使用 哑结点 指向原始头结点(这是因为题目 left 可能是 1,也就是说可能会删除头结点,对于这种情况一般就会创建虚拟头结点来解决)
  • 1)从 哑结点 开始走 left - 1 步,来到 left
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

川峰

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

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

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

打赏作者

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

抵扣说明:

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

余额充值