一念花开,一念花落。一念放下,万般自在。
多点时间努力,少点功夫矫情,要知道,前半生就算再美,后半生也要靠智慧和钱生活。哪怕你是对的,也不用非要证明别人是错的。对聪明人来说,解释是多余的,而对于蠢人来说,解释是不够的。所以,终归没有必要去解释什么。只做最好的自己。
前言
双指针解法在链表中尤为常见,通过双指针我们可以很快速的解决一些链表相关问题,例如查找链表节点,环形链表等相关题型。本文罗列了算法中基本全部的关于链表的双指针题型,学习完本文,再有这种题型必定手到禽来。
删除链表的倒数第 k 个结点 LeetCode 19题
这道题的解题思路其实我们需要进行拆开分析,首先我们需要找到这个倒数第 k 个结点,然后第二步就是将其删除。
其核心就是我们如何找到倒数第 k 个结点,这里我们就通过双指针的概念来进行快速查找。大致分为三步,如下图所示:
- 首先我们先让一个指针 p1 指向头结点,然后向前走 k 步。
- 接着我们再让另一个指针 p2 指向头节点。
- 第三步就是让 p1 和 p2 同时走 n-k 步,那么此时 p2 所在的位置就是倒数第 k 个结点。
通过这三步,我们只遍历了一次链表,就获得了倒数第 k 个 结点 p2。
这段代码如下:
ListNode findNode(ListNode head,int n){
ListNode p1,p2;
p1=p2=head;
for(int i=0;i<n;i++){
p1=p1.next;
}
while(p1!=null){
p1=p1.next;
p2=p2.next;
}
return p2;
}
复制代码
找到我们需要删除的结点后,我们就可以将其删除了,这里需要注意下,如果我们需要删除倒数第 k 个结点,那对我们来说,其实是先找出 倒数第 k+1 个结点。
先看代码:
public ListNode removeNthFromEnd(ListNode head, int k) {
//创建虚拟头结点,防止出现空指针的情况。
ListNode dummy=new ListNode(-1);
dummy.next= head;
//找到倒数第k+1个结点
ListNode node= findNode(dummy,k+1);
//移除 倒数第 k 个结点
node.next=node.next.next;
//返回结果
return dummy.next;
}
复制代码
这里创建虚拟头结点是为了避免空指针情况的发生,假如链表长度为5,我们需要删除倒数第5个结点也就是第一个结点,也就意味着我们要找到倒数第六个结点,但是第一个结点前面已经无结点,就会出错,利用虚拟头结点就可以避免这种情况发生。
单链表的中点 LeetCode 876题
给定一个头结点为
head
的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
通过上面题目对双指针的运用,再来解这道题就很容易啦,典型的通过快慢指针来实现,创建两个指针,slow 和 fast ,
初始化指向 head 结点,然后 slow 每走一步,fast 就走两步,等 fast 指针为空时,此时 slow就是在中间结点,当然,如果是偶数链表, slow是在中间两个结点偏右的结点。一起来看下代码:
public ListNode middleNode(ListNode head) {
//指定快慢指针
ListNode slow=head,fast=head;
//fast每次跑两步,slow每次跑一步,等fast泡完slow正好在中间节点
while(fast!=null&&fast.next!=null){
slow=slow.next;
fast=fast.next.next;
}
return slow;
}
复制代码
判断链表是否有环 LeetCode 141题
这道题的经典解法就是通过快慢指针来解决,一个跑得快,一个跑得慢,跑得的结点如果遇到 null ,那么说明链表不含环;如果含有环,快指针最终会超慢指针一圈。
public boolean hasCycle(ListNode head) {
ListNode fast=head;
ListNode slow=head;
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(fast==slow){
return true;
}
}
return false;
}
复制代码
已知链表中含有环,返回这个环的起始位置 LeetCode 142题
如图所示,这道题就是让我们找出环起点的位置,也就是3,这怎么找呢?
其实我们也是要利用快慢指针的方法来处理,还是基于head创建两个指针,slow和fast,然后我们设置slow每走一步,fa st 会走两步,那么假设 slow 走了k步,两者相遇,那么fast就一定走了 2k 步。
基于这个我们可以推导出什么呢?fast 比 slow 多走了 k 步,这个 k 步其实就是 fast 指针在圆环里转的圈,这个 k 值就是环长度的整数倍。
我们假设相遇点距圆环起点距离为 m ,环的起点距离头结点其实就是 k-m ,也就意味着 从头结点走 k-m 步就能到达环的起点。
如果从相遇点继续前进 k-m 步,也恰好到达环的起点,从这个信息我们可以得到什么?也就是当快慢指针相遇后,我们把其中一个指针重新指向 head ,再让两者同速前进 k-m 步就会再次相遇,此时相遇的地方就是环的起点。
好的,我们整体就分析完了,来上代码:
public ListNode detectCycle(ListNode head) {
ListNode slow,fast;
fast=slow=head;
// 快慢指针第一次相遇
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(slow==fast){
break;
}
}
if(fast==null||fast.next==null){
return null;
}
//将慢指针重置为 head
slow=head;
// 两者同速前进,走 k-m 步即可再次相遇
while(fast!=slow){
fast=fast.next;
slow=slow.next;
}
// 返回相遇时 slow的结点,也就是圆环的起始位置
return slow;
}
复制代码
关注小蛋,一起成长,一起进步