【跟着小蛋刷算法】双指针题型总结,学完本篇文章,双指针的题型都能拿下

210

一念花开,一念花落。一念放下,万般自在。

多点时间努力,少点功夫矫情,要知道,前半生就算再美,后半生也要靠智慧和钱生活。哪怕你是对的,也不用非要证明别人是错的。对聪明人来说,解释是多余的,而对于蠢人来说,解释是不够的。所以,终归没有必要去解释什么。只做最好的自己。

前言

双指针解法在链表中尤为常见,通过双指针我们可以很快速的解决一些链表相关问题,例如查找链表节点,环形链表等相关题型。本文罗列了算法中基本全部的关于链表的双指针题型,学习完本文,再有这种题型必定手到禽来。

删除链表的倒数第 k 个结点 LeetCode 19题

这道题的解题思路其实我们需要进行拆开分析,首先我们需要找到这个倒数第 k 个结点,然后第二步就是将其删除。

其核心就是我们如何找到倒数第 k 个结点,这里我们就通过双指针的概念来进行快速查找。大致分为三步,如下图所示:

image-20211130222512929

  1. 首先我们先让一个指针 p1 指向头结点,然后向前走 k 步。
  2. 接着我们再让另一个指针 p2 指向头节点。
  3. 第三步就是让 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题

image-20211130234436070

如图所示,这道题就是让我们找出环起点的位置,也就是3,这怎么找呢?

其实我们也是要利用快慢指针的方法来处理,还是基于head创建两个指针,slow和fast,然后我们设置slow每走一步,fa st 会走两步,那么假设 slow 走了k步,两者相遇,那么fast就一定走了 2k 步。

image-20211130234753403

基于这个我们可以推导出什么呢?fast 比 slow 多走了 k 步,这个 k 步其实就是 fast 指针在圆环里转的圈,这个 k 值就是环长度的整数倍。

我们假设相遇点距圆环起点距离为 m ,环的起点距离头结点其实就是 k-m ,也就意味着 从头结点走 k-m 步就能到达环的起点。

image-20211130235743648

如果从相遇点继续前进 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;
    }

关注小蛋,一起成长,一起进步