双指针 Two Pointers

第一类 快慢指针

快慢指针主要解决链表问题,两个指针一般都初始化指向链表的头结点,即fast = slow = head

前进时快指针 fast 在前,慢指针 slow 在后。即fast = fast.next.nextslow = slow.next

快慢指针定理:快指针走的长度永远是慢指针走的长度的 2 倍。

例题

  • LC141题:判定链表中是否包含环。

    题解:若链表含环,快指针最终会超慢指针一圈,和慢指针相遇,即说明链表含有环。

  • LC142题:返回链表开始入环的第一个节点。
    在这里插入图片描述
    题解:当快慢指针第一次相遇时,让其中任一个指针指向头节点,然后让它俩以同速前进,第二次相遇时所在的节点位置就是环的起点,此时返回任一个指针的位置都可以。

    设 3 个变量来表示这个链表里的几个重要长度:

     M:第一次相遇的点;
     L:环的length
     X:从链表头到环的起点的长度;
     Y:从环的起点到 M 点的长度;
     Z:从 M 点到环的起点的长度。
    

    相遇时快慢指针分别走了多少的长度呢?

     快指针:X+ Y + 假设走了 k 圈
     慢指针:X + Y
    

则由快慢指针定理得到以下表达式: 2 * (X + Y) = X + Y + kL ==> X + Y = kL
又因为 Y + Z = L,所以得 X = Z,即 从链表头到环的起点的长度 = 从 M 点到环的起点的长度。

所以当两个指针,一个从头开始走,一个从 M 点开始走时,相遇那点就是环的起点,证毕。

ListNode detectCycle(ListNode head) {
    ListNode fast, slow;
    fast = slow = 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;
    }
    slow = head;
    while(slow != fast){
        slow = slow.next;
        fast = fast.next;
    }
    return slow;
}
  1. LC876题:寻找链表的中间节点。

    题解:快指针走的速度是慢指针速度的2倍,当快指针指向单链表的尾部null时,慢指针所指位置即为链表的中点。【快的跑完一圈,慢的所指即为中】

  2. LC19题:删除链表的倒数第N个节点。

    题解:让快指针先走 n 步,然后快慢指针开始同速前进。这样当快指针走到链表末尾null时,慢指针所在位置就是倒数第 n 个链表节点。(1≤n≤链表长度)

    ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode fast, slow;
        fast = slow = head;
        // 快指针先前进 n 步
        // 注意此处写法
        while (n-- > 0) {
            fast = fast.next;
        }
        if (fast == null) {
            // 如果此时快指针走到头了,说明倒数第 n 个节点就是第一个节点
            return head.next;
        }
        // 让慢指针和快指针同步向前
        while (fast != null && fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        // slow.next 就是倒数第 n 个节点,删除它
        slow.next = slow.next.next;
        return head;
    }
    

    注意:循环 n 次,除了最常用的for (int i = 0; i < n; ++i) {...}之外,还可以写 while (n-- > 0) {...}while (--n >= 0) {...},此处可解释为执行while循环,每执行一次循环体则n减1 ,直到n=0时跳出循环。

第二类 左右指针

对撞指针

对撞指针用来解决数组问题,在数组中实际是指两个索引值,一般初始化为 left = 0, right = nums.length - 1 。此类方法有点像二分搜索。

例题

  1. LC881题:每艘船可以承载的最大重量为 limit,最多可同时载两人,返回载到每一个人所需的最小船数。

    题解:先将数组原地升序排序,设置一个计数器计算所需最小船数res。对撞指针分别移动,当两者之和小于等于船可以承载的重量limit时,左指针右移,否则右指针左移。在这个过程中,res都会自增加一。

int numRescueBoats(int[] people, int limit) {
    if(people == null || people.length == 0) { return 0; }
    Arrays.sort(people);
    int i = 0;
    int j = people.length - 1;
    int res = 0;
    while(i<=j){
        if(people[i]+people[j]<=limit){
            i++;
        }
        j--;
        res++;
    }
    return res;
}

固定间距指针(滑动窗口)

固定间距指针是指两个指针间距相同,步长相同,可以用来解决字符串问题。此方法与滑动窗口算法相同。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值