环形链表
我们在判断一个链表是否是环形的,即首尾相连,我们可以以使用快慢指针,如果快指针能再次追上慢指针,就说明该链表是环形的,这边可以举个操场跑步的例子,当操场是环形的,跑的快的,就可以对跑的慢的实现套圈.
判断环形链表
给你一个链表,如果其是环形链表,则返回 true 否则返回 false
存放到集合中
分析:通过集合里面查找来判断是否回到头指针,不建议
public boolean hasCycle(ListNode head) {Set<ListNode> set = new HashSet<>();while (head != null) {//如果重复出现说明有环if (set.contains(head))return true;//否则就把当前节点加入到集合中set.add(head);head = head.next;}return false;
}
快慢指针(快2慢1)
package LinkList;public class roundList {public class Solution {class ListNode {int val;ListNode next;public ListNode(int val, ListNode next) {this.val = val;this.next = next;}}public boolean hasCycle(ListNode head) {if (head == null){return false;}ListNode fast = head;ListNode slow = head;while (fast != null && fast.next != null){fast = fast.next.next;slow = slow.next;if (fast == slow){return true;}}return false;}}
}
先反转再比较
如果反转过后的头结点与原来的头结点相同,那么就是环形链表
反转原理:
- 先给头结点的下个结点赋值一个新结点 temp
- 然后把头结点的 next 指向一个新节点 newHead
- 然后把那个新节点 newHead 指向 头结点
- 然后再令头结点 为 temp (之前头结点的下一个结点)
在上面的流程中,temp充当相当于完成原来的链表的下个位置的指针,而newHead相当于是一种前驱指针的感觉,让原来在后面的元素指向前面的元素
public ListNode reverseList(ListNode head) {//新链表ListNode newHead = null;while (head != null) {//先保存访问的节点的下一个节点,保存起来//留着下一步访问的ListNode temp = head.next;//每次访问的原链表节点都会成为新链表的头结点,//其实就是把新链表挂到访问的原链表节点的//后面就行了head.next = newHead;//更新新链表newHead = head;//重新赋值,继续访问head = temp;}//返回新链表return newHead;}public boolean hasCycle(ListNode head) {ListNode rev = reverseList(head);if (head != null && head.next != null && rev == head) {return true;}return false;}
快慢指针的一般化
我们可以知道快指针要追上慢指针 需要多跑至少“一圈”,即一个链表长度,假设为m,当然所需要的时间就是 = 需要的圈数 / (快指针的速度 - 慢指针的速度)
为什么说多跑至少一圈?不妨想,快指针的速度 - 慢指针的速度 为偶数,而链表长度为奇数,那么就会出现第一次快指针追慢指针时,它们位置没有重合,而是快指针直接超越慢指针的情况,那么 快指针需要再追一圈,此时的圈数 = 2 * m,就一定为偶数就能追上
所以 所需要的时间就是 = 需要的圈数 / (快指针的速度 - 慢指针的速度) 必须为整数,当速度差分之圈数不是整数时,就需要多跑几圈来实现整除(即快慢指针的位置重合)
当然上面假设的环可能是从链表某处开始形成的环,有可能不是整个链表是环形
注意:循环的条件是快指针当前的不为null,如果要一次前进 m 步(即 fast = fast.m next)(m 个 next),那么就要保证其前面 m - 1个 next都不为null,否则会抛出空指针异常
代码(快3慢1)
ublic boolean hasCycle(ListNode head) {if (head == null)return false;ListNode slow = head;ListNode fast = head;while (fast != null && fast.next != null && fast.next.next != null) {slow = slow.next;fast = fast.next.next.next;if (slow == fast)return true;}return false;}
代码(快5慢1)
public boolean hasCycle(ListNode head) {if (head == null)return false;ListNode slow = head;ListNode fast = head;while (fast != null && fast.next != null && fast.next.next != null && fast.next.next.next != null && fast.next.next.next.next != null) {slow = slow.next;fast = fast.next.next.next.next.next;if (slow == fast)return true;}return false;}
代码(快3慢2)
public boolean hasCycle(ListNode head) {if (head == null)return false;ListNode slow = head;ListNode fast = head;while (fast != null && fast.next != null && fast.next.next != null) {slow = slow.next.next;fast = fast.next.next.next;if (slow == fast)return true;}return false;}
环形链表II
给你给定一个链表的头节点 head ,返回链表开始入环的第一个节点(在哪里开始形成环的)。 如果链表无环,则返回 null
分析
注:来源于魏梦舒老师的《算法漫画》
主要抓住两点,第一次相遇两指针的关系,一个走的路程是另外一个两倍
代码
public class roundListPlus {class ListNode{int val;ListNode next;public ListNode(int val) {this.val = val;next = null;}}public class Solution {public ListNode detectCycle(ListNode head) {ListNode fast = head;ListNode slow = head;// 快指针每次移动2,慢指针每次移动1// 用循环来寻找第一次相遇的点while (fast != null && fast.next != null){fast = fast.next.next;slow = slow.next;if (fast == slow){break;}}// 循环结束要么是该表非环形,要么是快慢指针相遇// 判断,如果非环形链表,直接返回 nullif ( fast == null || fast.next == null){return null;}// 根据递推关系,此时把其中一个指针放回头结点,另一个指针在第一次相遇位置// 此时两个指针一次都只移动1,下一次相遇就是入环结点slow = head;while (fast != slow){fast = fast.next;slow = slow.next;}return slow;}}
}