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

数组

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

 

java

复制代码

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

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

删除有序数组中的重复项

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

image.png

 

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]] 就是连续递增子序列。

image.png

思路分析:题目要求我们找的子序列是 连续 的,并且子序列里的元素要求 严格单调递增。在遍历的时候,从第 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
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值