在与链表有关的算法题中,经常会使用双指针去解决这些问题,而双指针中最常用到的是快慢指针,通过两个不同移动速率的指针来遍历链表。这种策略通常用于解决需要同时遍历链表中两个不同位置的问题。
快慢指针常见的用途包括:
- 1.判定链表是否有环: 使用快慢指针,如果存在环,快指针最终会追上慢指针。
- 2.找到链表的中间节点: 使用快慢指针,快指针每次移动两步,慢指针每次移动一步,当快指针到达链表末尾时,慢指针正好在中间。
- 3.判断回文链表: 使用快慢指针找到中间节点,然后反转后半部分链表,再逐一比较前半部分和后半部分的值是否相同。
1. LeetCode的876题:链表的中间结点
题目描述:
给你单链表的头结点 head ,请你找出并返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
- 利用快慢指针来解决,快指针一次循环走2步,慢指针一次循环走1步
- 当链表结点个数为奇数时,当快指针到最后一个结点时,慢指针一定是在链表的中间位置。例如{1,2,3,4,5}慢指针走一步到2时,快指针走两步到3慢指针再走一步到3时,快指针正好到5尾结点。
- 当链表结点个数为偶数时,其中间两个结点均为中间结点,但题目要求返回第二个结点,因此快指针则要遍历到末尾结点所指向的null为止。我们可以假设总共循环了n次,即快指针指向了位于2n+1的null,慢指针指向了位于n+1的结点,由于链表结点个数为2n所以前面一个的中间结点即为n,则此时的慢指针指向n+1即为第二个中间结点。
public ListNode middleNode(ListNode head) {
//例如{1,2,3,4,5}慢指针走一步到2时,快指针走两步到3
//慢指针再走一步到3时,快指针正好到5尾结点
if (head == null){
return null;
}
ListNode fast = head,slow = head;
//注意,此时的一定要判断fast.next是否为空
//否则后面fast=fast.next.next的时候会出现空指针异常
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
2. 寻找倒数第k个元素
题目描述:
输入一个链表`,输出该链表中倒数第k个节点。本题从1开始计数,即链表的尾节点是倒数第1个节点。
示例
给定一个链表: 1->2->3->4->5, 和 k = 2.
返回链表 4->5.
这题也是利用快慢指针来解决,只需要让fast指针先走k个结点,后slow再与fast一同走且每次循环走的步数相同。那么当fast走到null时,其与slow相差的结点个数依旧为k,再从链尾往前看,此时的slow即为倒数第k个节点。
public static ListNode getKthFromEnd(ListNode head, int k) {
ListNode fast = head;
ListNode slow = head;
//fast先走k步
while (fast != null && k > 0) {
fast = fast.next;
k--;
}
//再一同走直到fast到null
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
3. LeetCode的61题:旋转链表
题目描述
给你一个链表的头节点
head
,旋转链表,将链表每个节点向右移动k
个位置。
通过示例我们可以观察到rotate 2的链表是由第一行的链表截取{1,2,3}和{4,5}两部分,再调换顺序成{4,5}{1,2,3}后拼接得到的。
因此可以先找到倒数第k个节点所在的位置(这个上一题已经实现了),后以这个节点为头节点,将后面这段的尾节点的next值指向前一段的头节点,从而调换拼接成新的链表。
当然,这个倒数第k个节点的k一定要小于链表的len,因此k=k%len。当k == 0时,则不需要进行旋转,直接返回当前头节点。否则根据上面那题的方法找到倒数第k个节点,并调换再去拼接。
public ListNode rotateRight(ListNode head, int k) {
//这里要判断下k是否等于0
if(head == null || k == 0){
return head;
}
ListNode fast = head,slow = head,temp = head;
int len = 0;
//计算当前链表的长度
while(head != null){
head = head.next;
len++;
}
//保证k小于len
k %= len;
if(k == 0){
return temp;
}
//找到倒数第k个节点
while (fast != null && k > 0) {
fast = fast.next;
k--;
}
//此时的尾节点就为fast,slow为倒数k个节点前面一个
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
//将slow的后一个节点作为头结点
ListNode res = slow.next;
//一定要将翻转完的尾节点的next置为null
slow.next = null;
//将两段链表拼接起来
fast.next = temp;
//返回拼接后的链表
return res;
}