快慢指针的常见用法(一般都是一个走一步,另一个走两步,走k步的走法)
ps:那些双指针一个动一个时动时不动的这里没算是快慢指针,我这里写的就是那些特别明显的,比较经典的快慢指针用法
1.查找倒数第k个节点
快的先走k步,再两个开始一起走,这样快慢中间就差了k个节点,等快的走到最后的空,那慢的自然就是倒数第k个节点
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode fast=head,slow=head;
for (int i=0;i<k;i++)
fast=fast.next;
while (fast!=null) {
fast=fast.next;
slow=slow.next;
}
return slow;
}
}
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode fast=head,slow=head;
for (int i=0;i<n+1;i++) {
if (fast.next==null&&i!=n)
return head.next;
else
fast=fast.next;
}
while (fast!=null) {
fast=fast.next;
slow=slow.next;
}
slow.next=slow.next.next;
return head;
}
}
2.查看链表是否有环
快的走两步,慢的走一步。相遇则有环。
无环:自然快的走得快,慢的跟不上。有环:快的先进到环内,当快的走到慢的后面时,假设慢的在快的前面n步,此后两个每走一次,距离就减一,所以一定会相遇。
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode fast=head, slow=head;
while (fast!=null&&fast.next!=null) {
fast=fast.next.next;
slow=slow.next;
if (slow==fast)
return true;
}
return false;
}
}
这个快慢指针的解法官方题解图文并茂很清楚,不懂可看。
环形链表II力扣官方题解
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast=head, slow=head, heada=head;
while (fast!=null&&fast.next!=null) {
fast=fast.next.next;
slow=slow.next;
if (fast==slow)
break;
}
if (fast==null||fast.next==null) return null; //无环
while (heada!=slow) {
heada=heada.next;
slow=slow.next;
}
return slow;
}
}
3.找到链表中间节点
慢的走一步,快的走两步,快的到尾部的空时,慢的自然就才到中间n/2嘛,如果总数是偶数的话,是到中间的第二个。
class Solution {
public ListNode middleNode(ListNode head) {
ListNode fast=head, slow=head;
while (fast!=null&&fast.next!=null) {
fast=fast.next.next;
slow=slow.next;
}
return slow;
}
}
4.应用及其他类型
202. 快乐数
多写几个数可以发现不是快乐数的会形成环,检测有无环可以用快慢指针
class Solution {
public boolean isHappy(int n) {
int slow=n, fast=getNextHappyN(getNextHappyN(n));
while (fast!=1&&fast!=slow) {
slow=getNextHappyN(slow);
fast=getNextHappyN(getNextHappyN(fast));
}
return fast==1;
}
public static int getNextHappyN(int n) {
int next=0;
while (n!=0) {
next+=(n%10)*(n%10);
n/=10;
}
return next;
}
}
234. 回文链表
先找到中间,由中间向两边展开判断,同时需要把有半段反转
class Solution {
public boolean isPalindrome(ListNode head) {
ListNode fast=head, slow=head;
//找到中间节点
while(fast!=null&&fast.next!=null) {
fast=fast.next.next;
slow=slow.next;
}
//从中间开始反转链表
ListNode temp,pre=null;
while(slow!=null) {
temp=slow.next;
slow.next=pre;
pre=slow;
slow=temp;
}
//开始比较
while(pre!=null&&head!=null) {
if (pre.val!=head.val)
return false;
pre=pre.next;
head=head.next;
}
return true;
}
}
160. 相交链表
也不完全算是快慢指针吧。不过解法倒挺好理解,各自出发,走到头后换路,相交的部分都走过一遍,各自的不相交的部分也走过一遍,走的路一样多,可不就相遇了吗
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA==null||headB==null) return null;
ListNode pa=headA, pb=headB;
while (true) {
if (pa==pb) return pa;
if (pa.next==null&&pb.next==null) return null;
pa=pa.next==null ? headB:pa.next;
pb=pb.next==null ? headA:pb.next;
}
}
}
143. 重排链表
重排链表不属于上面三种类型,但也是要用到快慢指针,这个快慢指针用来拆分原链表,还是比较好想到的
下面两个不是特别明显的快慢指针,比如,一个一步步往前动,一个时动时不动:
283. 移动零
26. 删除有序数组中的重复项
等等