该系列会一直更新迭代,记录学习算法中的点点滴滴。经典题目也会持续更新。
双指针
通常,我们只需要一个指针进行迭代,即从数组中的第一个元素开始,最后一个元素结束。例如:for循环,map循坏,foreach循环等。 然而,有时为了提高搜寻效率我们会使用两个指针进行迭代,例如:字符串翻转。
示例
翻转数组中的元素。比如数组为 ['a','b','c','d'],反转之后变为 ['d','c','b','a']。
function tranArray(array) {
let left = 0,
right = array.length - 1
while (left < right) {
let temp = array[left]
array[left] = array[right]
array[right] = temp
left++
right--
}
return array
}
小结
双指针的使用场景一般有两种:
- 从两端向中间迭代数组
- 快慢指针 两种场景的技巧分别为:
一个指针从头部开始,而另一个指针从尾部开始。
确定两个指针的移动策略。
经典试题
两数之和 II - 输入有序数组
给定一个已按照 升序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 。
函数应该以长度为 2 的整数数组的形式返回这两个数的下标值。numbers 的下标 从 1 开始计数 ,所以答案数组应当满足 1 <= answer[0] < answer[1] <= numbers.length 。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
示例 1:
输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解题:
/**
* @param {number[]} numbers
* @param {number} target
* @return {number[]}
*/
var twoSum = function (numbers, target) {
// 经典头尾指针,left为数组最左指针,right为数组最右指针,循环至左右指针相碰
let left = 0,
right = numbers.length - 1
while (left < right) {
if (numbers[left] + numbers[right] < target) {
left++
} else if (numbers[left] + numbers[right] > target) {
right--
} else {
return [left + 1, right + 1]
}
}
};
移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1:
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。
解题:
/**
* @param {number[]} nums
* @return {number}
*/
var findMaxConsecutiveOnes = function (nums) {
let max = 0
slow = 0
for (let fast = 0; fast < nums.length; fast++) {
if (nums[fast] == 1) {
max = max > fast - slow + 1 ? max : fast - slow + 1
} else {
slow = fast + 1
}
}
return max
};
此题解法较多,这么解是为了更好的理解应用双指针。
长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
####### 示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
####### 解题
var minSubArrayLen = function (target, nums) {
// 快慢指针,题目要求为连续,故滑动窗口原理。sum大于target left指针++,反之right++
let left = 0,
rigt = 0
length = nums.length
sum = 0
res = Number.MAX_VALUE
while (rigt < length) {
sum += nums[rigt]
while (sum >= target) {
res = Math.min(res, rigt - left + 1)
sum -= nums[left]
left += 1
}
rigt++
}
return res == Number.MAX_VALUE ? 0 : res
};
三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解题
var threeSum = function (nums) {
nums.sort((a, b) => a - b)
let res = []
for (let i = 0; i < nums.length; i++) {
// 排序后第一个元素如果大于0则没有意义
if (nums[0] > 0) {
return res
}
// 去重
if (nums[i] != nums[i - 1]) {
let left = i + 1,
right = nums.length - 1
while (left < right) {
if (nums[left] + nums[right] + nums[i] === 0) {
res.push([nums[left], nums[right], nums[i]])
while (left < right && nums[left] == nums[left + 1]) left++; // 去重
while (left < right && nums[right] == nums[right - 1]) right--; // 去重
left++;
right--;
} else if (nums[left] + nums[right] + nums[i] < 0) {
left++
} else {
right--
}
}
}
}
return res
}
总结
双指针一般用于对数组的处理。场景:
- 碰撞指针:常用于有序数组,解决如:二分查找以及n数之和。
- 快慢指针:两个指针同时出发,快指针的逻辑决定慢指针的前进。
- 窗口滑动指针:与快慢指针类似,但常用于连续的区间场景。
摘录
- LeetCode: leetcode-cn.com/