双指针技巧
对于双指针,一般分为快慢指针和前后指针
前者一般使用在链表中,后者一般使用在数组中。
1 | class ListNode { |
快慢指针
快慢指针一般初始化两个指针分别指向链表的头结点 head
前进时快指针 fast
在前
慢指针 slow
在后
这就是快慢指针的核心
判断链表是否成环
单链表的特点是每一个节点只知道后置节点,不知道前置节点。如果我使用单指针想去解决这个判断成环
1 | public class Solution { |
当链表成环的时候,因为没有一个节点的后置节点为null
,所以该方法一定是死循环,无法退出。
经典解法就是使用两个指针,fast
指针跑得快,slow
指针跑得慢。
如果链表没有环,跑得快的那个指针最终会遇到 null
,说明链表不含环;
如果链表有环,快指针最终会超慢指针一圈,和慢指针相等,说明链表含有环。
以下是解法
1 | public class Solution { |
已知链表有环,返回环开始的地方
5
就是那个环开始的地方
假设快指针走了2k
步,则慢指针就一定走了k
步,那么k
其实就是环节点个数的整数倍
黄色的线就是快指针比慢指针多走的步数,即k
,图上只画了一圈,但是实际可能不止一圈
设慢指针在环中走了m
个节点。
则说明在环节点之前,一共有k-m
个节点,就是说我可以从头结点往后走k-m
次就可以找到该环开始的地方了。
但是如果想着如何把k-m
求出来的话是有点困难的,所以我们来看看关于快指针的路径中可不可以出现k-m
我们现在来看看快指针走的2k
长度的路径,它在环里面走过的路径长度是k+m
,从相遇点到相遇点的距离是k
那么就是说快指针从相遇点在环中走k-m
步就可以走到环开始的节点,
所以我们可以将slow置于head
,与fast同步前进,当两个指针相同时,返回该值
代码:
1 | public class Solution { |
寻找链表的中点
给出一个无环的链表,要求返回该链表的中点
使用快慢指针,当fast指针走到尾部的时候,slow就在链表中间位置了
以下是节点个数为奇数的情况
以下是节点个数为偶数的结果
可以发现,当个数为奇数的时候,慢指针正好在中心位置,
当个数为偶数的时候,慢指针在中心偏右的位置
这个算法在链表的归并算法中常常被用到
代码:
1 | public ListNode middleNode(ListNode head) { |
这是hash实现
1 | public ListNode middleNode(ListNode head) { |
删除倒数第n个节点
思路就是快指针先走n步,然后慢指针和快指针同步向前,当快指针的next是null的时候,慢指针的下一个就是倒数第n个节点
1 | class Solution { |
左右指针
左右指针一般初始化left right
两个变量来表示左边界和右边界
二分查找
这里就不赘述二分查找的细节了,之前有写,这里就写一种最常见的
查找target找到则返回索引,没有找到则返回-1
1 | int binarySearch(int[] nums, int target) { |
两数之和
题目中说,该数组是递增的,
通过left和right来调整大小从而判断是否存在两个数使得和为target
1 | class Solution { |
反转数组
这个就很简单了,直接上代码
1 | void reverseString(int[] arr) { |
滑动窗口
这个就是左右指针的重头戏了,掌握了这个方法后,可以解决一大类子字符串匹配的问题!
这个放在下一篇文章中详解