1.1 判断链表有环
这2道题都和判断链表是否有环相关,看下给的例子
Input: head = [3,2,0,-4], pos = 1
Output: true
Explanation: There is a cycle in the linked list, where tail connects to the second node.
对于这样一个带环的链表,判断是否有环,最简单的做法就是遍历所有的节点,如果遇到重复的节点,则说明有环,由此解法便是,用一个 visitedSet
来装已遍历过的节点,当遇到重复的节点,则说明有环,第一次遍历到的重复节点便是环的入口,这个解法需要额外提供一个 Set 空间,若是要求使用 O(1)
空间复杂度,就有了另外一个思路:
使用一个快指针和一个慢指针来遍历 list, 如果链表有环,则必然存在在某一个节点上2个指针相遇。
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) {
return false;
}
ListNode slow = head;
ListNode fast = head.next;
while (slow != fast) {
if (fast == null || fast.next == null) {
return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}
如果要找出环的入口位置呢?这里就涉及一点点数学的上换算了:
假设链表 head 到环入口的节点数为 a
(不包含入口节点),环的长度为b
, 慢指针走过的路程为 s
,快指针走过的路程为f
, 当2个指针第一次相遇时,则有如下关系:
- 第一次相遇:
- f = 2s (快指针每次走2步,慢指针每次走1步,所以相遇时路程是 2倍)
- f = s + k*b
这个公式就不太好理解, 假设相遇时的节点距离入口的距离为d, Slow 绕圈为 m, Fast 绕圈为 n, 则:
- s = a + d + m*b;
- f = a + d + n*b;
换算一下就得出,f = (n-m)*b + s; (n>m, 快指针绕圈更多),即 f = s + k * b;
我们将1,2 消一下就得到,s = k * b, 所以说慢指针走了环的 k 圈,若此刻有一个节点从 head 处开始走,它每次路过环入口的时机应该在 a + k * b
(k 为绕圈数),由于慢指针 s=k * b, 所以让一个节点从头开始走,当它们相遇时,即为环的入口了。
1.2 反转链表
还有一类经典的链表题是反转链表,自己做题的过程中发现要注意的点:
- 如何准确的处理节点之间的关系,先链接后面的关系(先使节点有多个父节点),再修改节点链接
- 知道哪个是 head 节点,比如全部反转的 list 来说,最后一个节点是新的Head, 对
swap nodes in pairs
来说新的 head 是第2个节点,对reverse nodes in k-gourp
来说,新的 head 是第一组的最后一个节点
- 那么对于全反转来说 prev 走到最后一个节点就是新的 Head
ListNode prev=null;
ListNode cur=head;
while(cur != null){
ListNode next = cur.next;
cur.next = prev;
prev = cur;
cur = next;
}
return prev;
- 对于不是最后指针的位置是新 head的,都使用
dummy
来链接新的 Head
ListNode dummy= new ListNode(-1);
dummy.next = head;
mmy` 来链接新的 Head
ListNode dummy= new ListNode(-1);
dummy.next = head;
当 reverse 完毕后, dummy.next
指向新的 head.