双指针
一、定义
双指针技巧主要分为两类:左右指针和快慢指针。所谓左右指针,就是两个指针相向而行或者相背而行;而所谓快慢指针,就是两个指针同向而行,一快一慢。
只要数组是有序的,或者是需要原地操作数组,都应该想到双指针技巧。
二、快慢指针
通常用于在有序数组/链表中去重,或者是对数组中的某些元素原地修改。
1、有序数组去重
slow指针为新数组(去重后的数组)的当前位置,而fast指针为原数组nums的当前位置。fast指针遍历原数组,当遇到新数组没有的元素就加入(即替换nums[slow] = nums[fast])slow指针向后走。因为数组是有序的,所以只需对比nums[fast]和nums[fast-1]是否相等就可以判断是否已经加入新数组(nums[fast-1]肯定比nums[fast]先加入)。
2、原地修改数组
与上题类似,slow指针为新数组(去重后的数组)的当前位置,而fast指针为原数组nums的当前位置。
3、寻找链表的倒数第k个节点
设置快慢指针起始为head,快指针先走k步(如果是删除倒数第k个节点,则先走k+1步先获取到pre节点),然后快慢指针一起走直到快指针走到末尾,此时慢指针指向的就是倒数第k个节点
4、判断链表是否成环或成环起始点
设置快慢指针起始为head,慢指针走一步,快指针就走两步,如果链表成环,快慢指针一定会再次相遇,如果快指针走到末尾都没有相遇则不成环。
如果成环,还可以求成环的起始点。两指针相遇时快指针比慢指针多走了一圈,设head到起始点为k,则一圈的长度也为k;
又设相遇点与起始点距离为m,则head到起始点 以及 相遇点运动一圈到起始点的距离都为k-m,此时将慢指针挪回head,两个指针再次相遇的地方就是起始点。
三、左右指针
前提是数组有序
例题有二分查找、两数之和、反转数组、回文串,这些题目都是比较明显可以看出需要使用左右指针的。
四、滑动窗口
滑动窗口其实是快慢指针的一种,但这里单独拿出来,是因为滑动窗口常用于求连续的子数组或子串,与前面的适用场景不同。
步骤:1、初始化窗口的左右边界left = right = 0,此时窗口是左闭右开的,包含0个元素
2、增大right来扩大窗口,直到达到要求 -- 找到可行解
3、增大left来缩小窗口,直到不满足要求,但每次增大left时,都要更新结果 -- 找到最优解
解题模板:
int left = 0, right = 0; while (right < s.size()) { // 增大窗口 window.add(s[right]); right++; while (window needs shrink) { // 缩小窗口 window.remove(s[left]); left++; } }
参考:labuladong 的算法小抄