参考题目:
做算法题的时候,要有这样的一种本能:当发现自己的代码里有两层循环时,先反思一下,能不能用空间换时间,把它优化成一层循环。
因为两层循环很多情况下都意味着 O(n^2)
的复杂度,这个复杂度非常容易导致你的算法超时。即便没有超时,在明明有一层遍历解法的情况下,你写了两层遍历,面试官对你的印象分会大打折扣。
大家记住一个结论:几乎所有的求和问题,都可以转化为 求差问题
。两数之和 就是一个典型的例子,通过把求和问题转化为求差问题,事情会变得更加简单。
定义两个指针,确定指针的开头和结尾。
解决双指针问题三种常用思想:
口诀:左右指针中间夹,快慢指针走到头,后序指针往回走
快慢指针一般都初始化指向链表的头结点 head
,前进时快指针 fast
在前,慢指针 slow
在后,巧妙解决一些链表中的问题。
快慢指针也是双指针,但是两个指针从同一侧开始遍历数组,将这两个指针分别定义为快指针(fast
)和慢指针(slow
),两个指针以不同的策略移动,直到两个指针的值相等(或其他特殊条件)为止,如 fast
每次增长两个,slow
每次增长一个。
伪代码:
const fn = function (head) {if (head === null || head.next === null) return false;let slow = head;let fast = head.next;while (slow !== fast) {if (fast === null || fast.next === null) {return false;}slow = slow.next;fast = fast.next.next;}return true;};
常见问题:
对撞指针是指在有序数组中,将指向最左侧的索引定义为左指针(left
),最右侧的定义为右指针(right
),然后从两头向中间相互迫近进行数组遍历。
对撞数组适用于 有序数组,也就是说当你遇到题目给定有序数组时,应该第一时间想到用对撞指针解题。
在数组中实际是指两个索引值,一般初始化为 left = 0
和 right = nums.length
。
伪代码:
const fn = function (nums) {let left = 0,right = nums.length - 1;while (left <= right) {if (条件判断) {// do somethingleft++;} else {// do somethingright--;}}};
相关题型:
什么时候需要联想到对撞指针?
这里我给大家两个关键字:有序
和 数组
没错,见到这两个关键字,立刻把双指针法调度进你的大脑内存。普通双指针走不通,立刻想对撞指针!
即便数组题目中并没有直接给出 有序
这个关键条件,我们在发觉普通思路走不下去的时候,也应该及时地尝试手动对其进行排序试试看有没有新的切入点。没有条件,创造条件也要上。
对撞指针可以帮助我们缩小问题的范围,这一点在 三数求和 问题中体现得淋漓尽致:因为数组有序,所以我们可以用两个指针 画地为牢 圈出一个范围,这个范围以外的值不是太大就是太小、直接被排除在我们的判断逻辑之外,这样我们就可以把时间花在真正有意义的计算和对比上。如此一来,不仅节省了计算的时间,更降低了问题本身的复杂度,我们做题的速度也会大大加快。
两个指针,一前一后组成滑动窗口,并计算滑动窗口中的元素的问题。
有序
和 数组
,应该先思考使用双指针解决,普通指针走不通则应该立即想到对撞指针排序
// 生成指定长度的数组并填充const res = new Array(n).fill(0);