一直有间断的刷算法题,从一开始的题海战术到现在的有针对性,也在不断的总结与思考,针对双指针算法从一开始的不知所措,到现在的有了一些自己的想法,下面就给大家介绍一下
首先什么是双指针呢(典型的就是快排)
Tips:双指针并不是固定的公式,而是一种思维方式~
-
顾名思义,双指针就是两个指针,一般我们会定义为p和q,基本思路就是通过两个指针遍历数组
-
两个指针的作用也很清晰,一般分为两种情况
- 同方向,一个负责遍历,一个负责赋值
- 两边同时进行遍历,比较之后再决定哪边先走,最后指针相遇,循环结束
举例说明
两个指针都相同的方向开始,一个负责遍历与比较一个负责等待被赋值(交换位置)
移动零
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
示例 2:
输入: nums = [0]
输出: [0]
首先定义p=0; q=0;
开始遍历数组,p指针是等待被交换的,q指针是进行遍历与比较的,q等于0时,q++,q不等于0时,p与q交换,同时p++,q++,当q遍历完整个数组是,p的位置之前就是所有非0的数,位置之后的是所有0的数。
图解
再增加一些边界条件,就可以完成了。
/**
* @param {number[]} nums
* @return {void} Do not return anything, modify nums in-place instead.
*/
var moveZeroes = function(nums) {
if(nums.length === 1) {
return nums
}
let p = 0;
let q = 0;
while(q < nums.length) {
if(nums[q] !== 0) {
let tmp = nums[q]
nums[q] = nums[p]
nums[p] = tmp
p++
}
q++
}
return nums;
};
删除有序数组中的重复项
给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
示例 1:
输入:nums = [1,1,2]
输出:2, nums = [1,2]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。
示例 2:
输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。
首先定义p=0; q=0;
开始遍历数组,p指针是等待被赋值的,q指针是进行遍历与比较的,nums[q]等于nums[p]时,q++,nums[q]不等于nums[p]时,nums[p+1]=nums[q],同时p++,q++,当q遍历完整个数组是,p+1就是最后数组的长度。
图解
最后的题解
/**
* @param {number[]} nums
* @return {number}
*/
var removeDuplicates = function(nums) {
if(nums.length === 1) {
return nums;
}
let p = 0;
let q = 0;
while(q < nums.length) {
if(nums[p] !== nums[q]) {
nums[p+1] = nums[q]
p++
}
q++
}
return p+1;
};
是不是感觉找到一些规律了??没错,这就是双指针的关键点,再来看下双指针的另一种情况
滑动窗口
滑动窗口是一种特殊的双指针操作,两个指针从一个方向一起走,一个指针遍历,一个指针等待,如果发现满足条件,等待的指针向前移动。
无重复字符的最长子串
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
示例 1:
输入: s = "abcabcbb"输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例 2:
输入: s = "bbbbb"输出: 1解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例 3:
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
图解
题解
var lengthOfLongestSubstring = function(s) {
let ans = 0
let p = 0, q = 0;
let set = new Set()
while(q < s.length) {
if(!set.has(s[q])) {
ans = Math.max(ans, q - p +1)
set.add(s[q])
q++
} else {
set.delete(s[p])
p++
}
}
return ans;
};
两个指针都从相反的方向开始,一个负责遍历与比较一个负责等待被赋值(交换位置)一般是找最大最小
盛最多水画的容器
给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明:你不能倾斜容器。
输入:[1,8,6,2,5,4,8,3,7]
输出:49
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。示例 2:
示例 2:
输入: height = [1,1]输出: 1
示例 3:
输入: height = [4,3,2,1,4]输出: 16
示例 4:
输入: height = [1,2,1]输出: 2
图解
题解
关键点:双指针两边查找
横坐标:q-p
纵坐标:Math.min(nums[p], nums[q])
var maxArea = function(height) {
let p = 0;
let q = height.length - 1;
let ans = 0
while(p < q) {
let x = q - p;
let y = Math.min(height[p], height[q]);
let area = x*y;
ans = Math.max(ans, area)
if(height[p] <= height[q]) {
p++
} else {
q--
}
}
return ans;
};
两数之和 II - 输入有序数组
给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。
以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
你所设计的解决方案必须只使用常量级的额外空间。
示例 1:
输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2]
示例 2:
输入:numbers = [2,3,4], target = 6
输出:[1,3]
解释:2 与 4 之和等于目标数 6 。因此 index1 = 1, index2 = 3 。返回 [1, 3]
示例 3:
输入:numbers = [-1,0], target = -1
输出:[1,2]
解释:-1 与 0 之和等于目标数 -1 。因此 index1 = 1, index2 = 2 。返回 [1, 2]
图解
题解
关键点:两个指针从两边开始,计算指针位置相加的值,等于目标值,停止,大于则将右指针左移,小于将左指针右移。
/**
* @param {number[]} numbers
* @param {number} target
* @return {number[]}
*/
var twoSum = function(numbers, target) {
let p = 0;
let q = numbers.length -1;
while(p < q) {
let sum = numbers[p] + numbers[q];
if(sum > target) {
q--
} else if(sum < target) {
p++
} else {
return [p+1, q+1]
}
}
};
双指针中的快慢指针
快慢指针,是使用速度不同的指针(可用在链表、数组、序列等上面),来解决一些问题。
通俗点的比喻是操场跑圈,跑的快的人迟早会追上跑的慢的人,所以要判断是否是环形链表,只需要使用快慢指针即可
解题思路是快指针(p)多走几步,慢指针(q)少走几步,两个指针相遇则是环形链表,否则则不是
举例说明
解决环形链表、环形数组一类问题
环形链表
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
输入: head = [3,2,0,-4], pos = 1
输出: true
解释: 链表中有一个环,其尾部连接到第二个节点。
输入: head = [1,2], pos = 0
输出: true
解释: 链表中有一个环,其尾部连接到第一个节点。
输入: head = [1], pos = -1
输出: false
解释: 链表中没有环。
解题代码
var hasCycle = function(head) {
// 定义快慢指针
let p = head;
let q = head;
// 利用快指针遍历链表,如果不存在环则快指针将会提前走完链表,否则当快慢指针相遇时,确定为环形链表
while(p !== null && p.next !== null) {
q = q.next;
p = p.next.next;
if(p === q) {
return true;
}
}
return false;
};