双指针的解题思路在做链表问题中真的是屡试不爽!让我用几道简单的算法题来带大家了解一下什么是双指针。
先看一个简单的问题:寻找一个链表中的中间节点。
正常我们的解题思路是怎样的?首先我们要获得链表的长度,再遍历链表到链表长度的一半,最终获得链表中间的节点。
看看用双指针是如何解决的。首先我们先定义一个快指针(fast)和一个慢指针(slow),他们最开始指向的都是链表的头结点,然后开始遍历头结点。不同的是每次循环后我们让慢指针移动一个,即slow=slow.next。而让快指针每次移动两个,fast=fast.next.next。这样处理有什么好处呢?就是当你的fast到达了终点时你的slow才只跑了半程,此时slow所在的节点不就是中间节点吗?浅浅画个图:
看看代码可能你就懂了。
public ListNode middleNode(ListNode head){
ListNode slow = head; //慢指针
ListNode fast = head; //快指针
while (fast !null &fast.next !null){
slow slow.next;
fast fast.next.next;
}
return slow;
}
如果你认为双指针只能做这些那你就错啦!让我们想想链表的缺点,相比于数组他没法直接拿到一个节点的数据,要想拿到一个特定节点,你必须要从头结点开始遍历。那一个数据还好,我要多拿几个节点岂不是没次都要遍历一次。想想双指针的思想是不是能不能帮到我们来优化这个操作。
再看一题:删除链表中倒数第n个节点。
正常思路:我们肯定要先拿到n前面那个节点吧,再将他的next指向n的next。要想拿到n前面的节点要算链表的长度吧,要遍历链表吧。
双指针:我们想得到n前面那个节点,也就是希望当fast跑到终点以后是,slow能停在n前面那个节点上,对吧!怎么实现?让fast先跑n+1个节点不就得了。
这次直接看代码
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode temp = new ListNode(-1);
temp.next=head;
//快慢指针
ListNode slow =temp;
ListNode fast =head;
//先让快指针领先慢指针n个
while(fast!=null && n>0){
fast=fast.next;
n--;
}
while(fast!=null){
slow=slow.next;
fast=fast.next;
}
slow.next=slow.next.next;
ListNode ans = temp.next;
return ans ;
}
}
有人发现了,怎么跟上一题不一样啦?之前不是slow和fast都是head,这个slow怎么等于temp,并且temp.next还等于head了?思考一下为什么?嘿嘿!看看题,我们是要是要删除节点的呀,如果链表中就只有一个节点(头节点),要删掉头结点怎么办,把slow和fast都是head的话头结点还能删掉吗?所以必须在头结点前再插入一个元素,要是头结点被删了直接temp.next=null,不就妥啦!这里需要注意哦!!
再看看代码,发现通过我们这样的赋值ListNode slow =temp;ListNode fast =head;slow已经比fast慢了一步啦!不过想想好像正好,我们想要的是n前面的那个节点,此时我在让fast先跑n个位置是不是就解决啦。画个图(假设要删除倒数第3个节点)
(注:fast为啥会跑出去?因为根据我们的代码fast=null循环才会停止,fast最后停止的节点是尾节点的下一个节点也就是null)
通过这个题的思路我们能想到什么?我们是不是可以通过slow和fast指针的关系能通过双指针能直接获得某个节点的信息,再也不用遍历两次链表啦。
同样类似的题如:删除重复的节点,删除特定的节点,获得倒数第N个节点等都可以用双针来解决,或许他并不是最优解法但也能为我们拓宽思路,不是吗?
在来最后一题拓展题:合并两个链表。
这道题很简单!但是你可以试试用双指针能不能做呢?
下面是我的解法:
public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
ListNode temp=new ListNode(0);
temp.next = list1;
//双指针
ListNode slow =temp;
ListNode fast =list1;
//让fast领先(b-a)
int len=b-a;
while(fast!=null && len>0){
fast=fast.next;
len--;
}
while(a>0){
slow=slow.next;
fast=fast.next;
a--;
}
slow.next = list2;
while(list2.next!=null){
list2=list2.next;
}
list2.next=fast.next;
return temp.next;
}
“双指针解决链表问题好方便啊!”这是我在学习完双指针后的感触!双指针不仅在链表中有,数组中同样也有。通过双指针的思想我是不是可以大胆的扩展三指针?四指针?来通过一次遍历获得链表中的多个节点呢?算法最终要学的是他的思想,如何解题,如何想到这个方法的,不断的拓宽我们的思维。
希望大家看完我这三道算法题的讲解后能对你有所帮助,有所收获。当你熟练掌握双指针的话,我相信他一定能成为你在解决链表问题的大杀器!!拜拜!