一.何为快慢指针
快慢指针指的是定义两个指针,一个指针移动速度快(称之为快指针),一个指针移动速度慢(称之为慢指针),这样之间就会产生我们想要的差值,然后通过这种差值我们就可以找到我们想要的节点.一般我们会让快指针一次走两步,慢指针一次走一步.(为防止空指针异常).
二.利用快慢指针的面试题
1.寻找中间节点
我们首先定义一个fast快指针,然后定义一个slow慢指针.让slow每次走一步,fast每次走两步.当fast走到空的时候,slow节点就是中间节点.
![](https://i-blog.csdnimg.cn/blog_migrate/e71cfd0939dad8f44eb065c1c8e83661.png)
![](https://i-blog.csdnimg.cn/blog_migrate/02ecce53ce17bdfa53a9771e26fe322e.png)
![](https://i-blog.csdnimg.cn/blog_migrate/ba575f132fc1bee05ba079b9f3f6967c.png)
代码如下:
//寻找中间节点
public ListNode middle(ListNode head){
if (head==null){
return null;
}
ListNode slow=head;
ListNode fast=head;
while(fast!=null&&fast.next!=null){
slow=slow.next;
fast=fast.next.next;
}
return slow;
}
2.链表中的倒数第k个节点
很多同学就会想到既然是求倒数的第k个节点,那么就先可以把链表的总长度求出来,然后用链表的长度减去k,得到的差值就是我们要走的步数.对于这种解法是可以实现,但是就说明要遍历两次这个链表,时间复杂度就比较高.为了达到面试的高度,我们可以使用快慢指针实现这一题.
快慢指针的思想:首先我们让快指针走k-1步(此时我们要判断一下k的值是否合法,k<0的情况或者k>链表的长度的情况).
然后让fast和slow同时走,当fast==null || fast.next==null的时候我们就停止
往后走,此时slow为倒数第k个节点.
如下图所示:
![](https://i-blog.csdnimg.cn/blog_migrate/6003572ea42b619cd89cc077cdeb4021.png)
![](https://i-blog.csdnimg.cn/blog_migrate/270d6941e049dd7783821bea3207201c.png)
![](https://i-blog.csdnimg.cn/blog_migrate/1eae4dc0688f23ae75c37ac8bd1b76d9.png)
代码如下:
//链表中倒数第k个结点
public ListNode FindKthToTail(ListNode head,int k) {
ListNode slow=head;
ListNode fast=head;
if(head==null||k<=0){
return null;
}
//让快指针先走k-1步
while(k-1!=0){
fast=fast.next;
if(fast==null){
return null;
}
k--;
}
while(fast!=null&&fast.next!=null){
slow=slow.next;
fast=fast.next;
}
return slow;
}
3.判断链表是否有环
有同学可能会想,一个链表怎样才能算一个环呢?我们来,上图给同学.
![](https://i-blog.csdnimg.cn/blog_migrate/540a92dca7a62acaee056a5d67e544dc.png)
这一题我们还是需要利用快慢指针的方法,让快指针一次走两步,慢指针一次走一步.如果这个链表是带环的,slow和fast是会遇到(及slow=fast),这就是类似我们数学当中的追及问题.
//判断一个链表是否有环
public boolean hasCycle(){
if (head==null){
return false;
}
ListNode fast=head;
ListNode slow=head;
while(fast!=null&&fast.next!=null){
slow=slow.next;
fast=fast.next.next;
//说明走到相等了
if (fast==slow){
return true;
}
}
//如果链表走到了空说明没有环
return false;
}
4.寻找第一个入环地址
首先我们还是要判断这个链表是否有环,就是上一题的代码.如果确定有环,就让slow指针指向链表的头位置,然后fast和slow没人一步走,当fast等于slow这就是第一个入环的地址.
//寻找第一个入环地址
public ListNode detectCycle(){
if (head==null){
return null;
}
ListNode fast=head;
ListNode slow=head;
while(fast!=null&&fast.next!=null){
slow=slow.next;
fast=fast.next.next;
//找到相遇点
if (fast==slow){
break;
}
}
if (fast==null&&fast.next!=null){
return null;
}
slow=head;
//走到相等就是第一个入环地址
while(slow!=fast){
fast=fast.next;
slow=slow.next;
}
return slow;
}
5.判断一个链表是否回文
首先我们要知道什么是回文结构?回文,知道回文结构之后,我们就要开始对链表进行操作了,首先我们要找到这个链表的中间节点(利用快慢指针),然后反转中间节点之后的链表,最后开始判断是否回文.
还要注意一点是:在找节点之前要判断head是否为null,为null就需要返回一个null.
只有一个节点时就是一个回文结构.
反转之后的结构:
![](https://i-blog.csdnimg.cn/blog_migrate/88b5a7ba42da5f1a3de927c94625947f.png)
然后让head和slow同时走,如果走到相等就是一个回文结构.但是要注意偶数的情况的话head.next=slow就是一个回文结构.
//判断一个链表是否为回文的链表
public boolean chkPalindrome(ListNode head) {
// write code here
if(head==null){
return true;
}
if(head.next==null){
return true;
}
//1.首先要找到中间节点:利用快慢指针
ListNode slow=head;
ListNode fast=head;
while(fast!=null&&fast.next!=null){
slow=slow.next;
fast=fast.next.next;
}
//循环走完slow就是中间节点
//2.然后反转slow之后的链表
ListNode cur=slow.next;
while(cur!=null){
ListNode curNext=cur.next;
cur.next=slow;
slow=cur;
cur=curNext;
}
//开始判断是否回文
while(head!=slow){
if(head.val!=slow.val){
return false;
}
//用来判断链表为偶数的情况
if(head.next==slow){
return true;
}
head=head.next;
slow=slow.next;
}
return true;
}
以上是我对快慢指针的一些面试的理解,希望对大家有所帮助,如有错误望大家指出.