一、理论基础
双指针法又称为“快慢指针法”,可以通过一个快指针和一个慢指针在一个 for 循环下完成两个 for 循环的工作。
- 快指针:寻找新数组元素,新数组就是不含有目标元素的数组
- 慢指针:指向更新新数组下标的位置
二、 相关题目
首先需要对数组进行排序,然后有一个 for 循环,确定第一个数i。第二个数和第三个数使用双指针法,第二个数起始位置指向 i+1,第三个数起始位置指向数组的最后一个元素,计算这三个数的和 sum。
- 第一种情况,sum > 0,说明结果大了,需要把第三个数向前位移一位,即 right--,这样才能让结果小一些,更加接近 0
- 第二种情况,sum < 0,说明结果小了,需要把第二个数向后位移一位,即 left++,这样才能让结果大一些,更加接近 0
- 第三种情况,sum = 0,把结果储存到结果集里,并把第二个数向后位移一位,第三个数向前位移一位,即 left++; right--; 为什么要同时操作 left 和 right 呢,因为这三个数的结果已经是 0 了,如果只操作 left ,那么下一轮 while 循环的三数之和必定 > 0,同理,如果只操作 right,那么下一轮 while 循环的三数之和必定 < 0,换言之,这轮循环没有必要进行,我们同时移动这两个指针,把多余的循环跳过即可。
直到遍历完整个数组。题目要求结果集中不能有重复的数组,所以需要对结果集去重。 如何去重呢?如果 nums[i ] 和 nums[i - 1]的值相等,是不是就说明 nums[i] 的情况我们已经算过了,就不需要再算一遍了,此时,直接跳过该次循环。同理,left 和 right 也是如此。
时间复杂度为:O(n²)
/**
* @param {number[]} nums
* @return {number[][]}
*/
var threeSum = function(nums) {
const res = [];
// 排序
nums.sort((a, b) => a - b);
for (let i = 0; i < nums.length; i++) {
// 对第一个数去重,即i
if (i > 0 && nums[i] === nums[i - 1]) {
continue
}
if (nums[i] > 0) break
let left = i + 1
let right = nums.length - 1
while (left < right) {
// 对第二个数去重,即left
if (left > i + 1 && nums[left] === nums[left - 1]) {
left++
continue
}
// 对第三个数去重,即right
if (right < nums.length - 2 && nums[right] === nums[right + 1]) {
right--
continue
}
const sum = nums[i] + nums[left] + nums[right]
if (sum < 0) {
left++
} else if (sum > 0) {
right--
} else {
res.push([nums[i], nums[left], nums[right]])
left++
right--
}
}
}
return res;
};
该题与上一题的思路一致,不过需要先嵌套 for 循环确定第一个数 i 和第二个数 j,之后的思路与上一题一致,重点在于结果集去重。
时间复杂度:O(n³)
/**
* @param {number[]} nums
* @param {number} target
* @return {number[][]}
*/
var fourSum = function(nums, target) {
nums.sort((a, b) => a - b)
if (nums.length < 4) {
return []
}
const res = []
for (let i = 0; i < nums.length; i++) {
// 第一个数去重
if (i > 0 && nums[i] === nums[i - 1]) continue
for (let j = i + 1; j < nums.length; j++) {
// 第二个数去重
if (j - i > 1 && nums[j] === nums[j - 1]) continue
let left = j + 1
let right = nums.length - 1
while (left < right) {
// 第三个数去重
if (left - j > 1 && nums[left] === nums[left - 1]) {
left++
continue
}
// 第四个数去重
if (right < nums.length - 1 && nums[right] === nums[right + 1]) {
right--
continue
}
const sum = nums[i] + nums[j] + nums[left] + nums[right]
if (sum > target) {
right--
} else if (sum < target) {
left++
} else {
res.push([nums[i], nums[j], nums[left], nums[right]])
left++
right--
}
}
}
}
return res
};
本题仍然可以使用双指针法。由于数组是升序排列的,所以我们可以维护一个快指针 right,负责检查元素是否和上一个元素重复,维护一个慢指针 left,用来储存不重复的元素。
- 如果 right 指向的元素和上一个元素不重复,将 right 指向的元素储存到 left 指向的位置,快慢指针同时向右走一步;
- 如果 right 指向的元素和上一个元素不重复,那么直接将 right 向右走一步,检查下一个元素
数组的第一个元素无论如何都不会重复,所以可以直接从第二个元素开始。
/**
* @param {number[]} nums
* @return {number}
*/
var removeDuplicates = function(nums) {
let left = 1
for (let right = 1; right < nums.length; right++) {
if (nums[right] !== nums[right - 1]) {
nums[left++] = nums[right]
}
}
return left
};
此题跟上题思路一致,快指针用来检测值是否和 val 相等,慢指针则用来记录值不等于 val 的元素。但是此题需要从第一个元素开始检测。
/**
* @param {number[]} nums
* @param {number} val
* @return {number}
*/
var removeElement = function(nums, val) {
let left = 0
for (let right = 0; right < nums.length; right++) {
if (nums[right] !== val) {
nums[left++] = nums[right]
}
}
return left
};