【双指针】实战:快慢指针(2)

上一篇是对双指针套路的总结,后面的几篇对 双指针 的各点进行实战的练习。 本篇以 快慢指针 为主。

数组

首先先复习一下 快慢指针 的套路:

java 复制代码
int slow = 0, fast = 0;
while(fast < nums.length){
    if ( ... ){
        // todo 缩小范围
        slow++;
    }
    // todo ... 扩大范围
    fast++;
}

下面根据上面的模板,给出几道来自leetcode算法题:

删除有序数组中的重复项

给你一个 非严格递增排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。

java 复制代码
public int removeDuplicates(int[] nums) {
    // 定义一下两个变量(快慢指针)
    int slow = 0, fast = 0;
    // 变量边界范围
    while(fast < nums.length){
        if (nums[fast] != nums[slow]){
            // 缩小范围
            slow++;
            nums[slow] = nums[fast];
        }
        // 默认一直都在扩大范围
        fast++;
    }
    return slow+1;
}

最长连续递增序列

给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。

连续递增的子序列 可以由两个下标 l 和 r(l < r)确定,如果对于每个 l <= i < r,都有 nums[i] < nums[i + 1] ,那么子序列 [nums[l], nums[l + 1], ..., nums[r - 1], nums[r]] 就是连续递增子序列。

思路分析:题目要求我们找的子序列是 连续 的,并且子序列里的元素要求 严格单调递增。在遍历的时候,从第 2 个元素开始;

  • 如果当前遍历到的元素比它左边的那一个元素要严格大,「连续递增」的长度就加 1;
  • 否则「连续递增」的起始位置就需要重新开始计算。
java 复制代码
public int findLengthOfLCIS(int[] nums) {
    // 定义快慢指针变量
    int slow = 0, fast = 0;
    // 定义长度变量
    int res = 1;
    // 遍历边界范围
    while (fast < nums.length){
        if (fast > 0 && nums[fast-1] >= nums[fast]){
            res = Math.max(res,fast-slow);
            slow = fast;
        }
        fast++;
    }
    return Math.max(fast-slow,res);
}

链表

对于对链表而言,其实思想是一样的,其大致框架就是将数组替换成了链表而已。

java 复制代码
// 定义快慢指针
ListNode slow = head, fast = head;
// 对于快慢指针,我们需要考虑的就是快指针不要越界
while (快指针的范围){
    fast = fast.next.next;
    slow = slow.next;
    // 结束条件
    if (结束条件){
        // todo 操作
    }
}

环形链表

给你一个链表的头节点 head ,判断链表中是否有环。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。

如果链表中存在环 ,则返回 true 。 否则,返回 false 。

java 复制代码
public boolean hasCycle(ListNode head) {
    // 考虑特殊情况:只有一个元素 不存在元素 时,一定不会有循环
    if (head == null || head.next == null){
        return false;
    }
    // 定义快慢指针
    ListNode slow = head, fast = head;
    // 对于快慢指针,我们需要考虑的就是快指针不要越界
    while (fast != null && fast.next != null){
        fast = fast.next.next;
        slow = slow.next;
        // 结束条件:快指针 = 慢指针
        if (fast == slow){
            return true;
        }
    }
    // 如果快指针有结束情况,说明一定不循环
    return false;
}

注意:为什么不是先判断 if (fast == slow) 而是 先移动指针? 其实答案挺明显的,如果我们先判断,那么就算是无环的时候,你也会发现fast == slow,那这不就一直返回 true 了嘛,显然不合理。

删除链表的倒数第 N 个结点

题目:

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

示例 1:

输入:head = [1,2,3,4,5], n = 2

输出:[1,2,3,5]

示例 2:

输入:head = [1], n = 1

输出:[]

示例 3:

输入:head = [1,2], n = 1

输出:[1]

提示:

  • 链表中结点的数目为 sz
  • 1 <= sz <= 30
  • 0 <= Node.val <= 100
  • 1 <= n <= sz

进阶:你能尝试使用一趟扫描实现吗?

java 复制代码
public ListNode removeNthFromEnd(ListNode head, int n) {
    if(head == null) return head;
    ListNode dummy = new ListNode(-1);
    dummy.next = head;
    ListNode slow = dummy, fast = dummy;
    while(n > 0){
        fast = fast.next;
        n--;
    }
    while(fast.next != null){
        slow = slow.next;
        fast = fast.next;
    }
    slow.next = slow.next.next;
    slow = slow.next;
    return dummy.next;
}

链表的中间结点

对于链表而言,找中点判断是否是回文,其实都是可以使用快慢指针实现的。

下面再看一下这道找中点的题目,其实就是快指针每次移动的速度都是慢指针的两倍:

题目 给你单链表的头结点 head ,请你找出并返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

示例 1:

输入:head = [1,2,3,4,5]

输出:[3,4,5]

解释:链表只有一个中间结点,值为 3 。

示例 2:

输入:head = [1,2,3,4,5,6]

输出:[4,5,6]

解释:该链表有两个中间结点,值分别为 3 和 4 ,返回第二个结点。

java 复制代码
public ListNode middleNode(ListNode head) {
    if(head == null || head.next == null){
        return head;
    }
    ListNode slow = head, fast = head;
    while(fast.next != null && fast.next.next != null){
        slow = slow.next;
        fast = fast.next.next;
    }
    if(fast.next != null){
        return slow.next;
    }else{
        return slow;
    }
}

总结

本章的快慢指针到整理就结束了,总结一下快慢指针的使用场景:

  • 数组
  • 链表

上面两种类型是比较常见的。

快慢指针 主要思想就是通过两个指针一前一后去判断。与二分查找不同。二分查找是首尾(左右)两个指针。

快慢指针可以解决

  • 循环问题
  • 重复问题
  • 最值问题(长度,一般争对数组)
相关推荐
代码雕刻家22 分钟前
数据结构-3.9.栈在递归中的应用
c语言·数据结构·算法
雨中rain22 分钟前
算法 | 位运算(哈希思想)
算法
2402_8575893626 分钟前
“衣依”服装销售平台:Spring Boot框架的设计与实现
java·spring boot·后端
吾爱星辰1 小时前
Kotlin 处理字符串和正则表达式(二十一)
java·开发语言·jvm·正则表达式·kotlin
哎呦没2 小时前
大学生就业招聘:Spring Boot系统的架构分析
java·spring boot·后端
Kalika0-02 小时前
猴子吃桃-C语言
c语言·开发语言·数据结构·算法
编程、小哥哥2 小时前
netty之Netty与SpringBoot整合
java·spring boot·spring
sp_fyf_20242 小时前
计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-10-02
人工智能·神经网络·算法·计算机视觉·语言模型·自然语言处理·数据挖掘
IT学长编程3 小时前
计算机毕业设计 玩具租赁系统的设计与实现 Java实战项目 附源码+文档+视频讲解
java·spring boot·毕业设计·课程设计·毕业论文·计算机毕业设计选题·玩具租赁系统
莹雨潇潇3 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器