15. [双指针]三数之和
15. 三数之和
方法一:排序+双指针
不重复的要求使我们不能无脑地使用三重循环枚举所有三元组。举个例子,数组中所有元素都为0,即:
[0, 0, 0, 0, 0, 0, 0, 0, ..., 0, 0]
这种方法的时间复杂度仍然为 \(O(N^3)\),,毕竟我们还是没有跳出三重循环的大框架。然而它是很容易继续优化的,可以发现,如果我们固定了前两重循环枚举到的元素 \(a\) 和 \(b\),那么只有唯一的 \(c\) 满足 \(a+b+c=0\)。当第二重循环往后枚举一个元素 \(b^{’}\)时,由于\(b^{’}>b\),那么满足 \(a+b^{'}+c^{'}=0\) 的 \(c^{'}\) 一定有 \(c^{'}<c\),即\(c^{'}\)在数组中一定出现在 \(c\) 的左侧。也就是说,我们可以从小到大枚举 \(b\),同时从大到小枚举 \(c\),即第二重循环和第三重循环实际上是并列的关系。
所以在遍历寻找 \(b,c\) 时,我们可以使用「双指针」法。在枚举的过程每一步中,「左指针」会向右移动一个位置(也就是题目中的 \(b\)),而「右指针」会向左移动若干个位置,这个与数组的元素有关,但我们知道它一共会移动的位置数为\(O(N)\),均摊下来,每次也向左移动一个位置,因此时间复杂度为可以从 \(O(N^2)\)优化为\(O(N)\)。
// 执行用时: 25 ms , 在所有 Java 提交中击败了 59.96% 的用户
// 内存消耗: 43.3 MB , 在所有 Java 提交中击败了 12.15% 的用户
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
int n = nums.length;
Arrays.sort(nums); //
List<List<Integer>> res = new ArrayList<List<Integer>>();
// 枚举 a
for (int first = 0; first < n; first++){
// a 需要和上次枚举的数不同
if (first > 0 && nums[first] == nums[first - 1]){
continue;
}
// c 对应的指针初始化
// 指向数组的最右端
int third = n - 1;
int target = -1 * nums[first];
// 枚举 b
for(int second = first + 1; second < n; second++){
// b 需要和上次枚举的数不同
if(second > first + 1 && nums[second] == nums[second - 1]){
continue;
}
// 保证 b 的指针在 c 的指针的左侧
while (second < third && nums[second] + nums[third] > target){
third--;
}
// 双指针重合,不再会有满足 a+b+c = 0 的 c 了,可以退出循环
if(second == third){
break;
}
if(nums[second] + nums[third] == target){
List<Integer> list = new ArrayList<Integer>();
list.add(nums[first]);
list.add(nums[second]);
list.add(nums[third]);
res.add(list);
}
}
}
return res;
}
}
下面是可稍作修改就应用于 18. 四数之和 的模板。
// 执行耗时:44 ms,击败了12.87% 的Java用户
// 内存消耗:43.3 MB,击败了10.04% 的Java用户
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
int n = nums.length;
Arrays.sort(nums); //
List<List<Integer>> res = new ArrayList<List<Integer>>();
int target = 0;
// 枚举 a
for (int first = 0; first < n - 2; first++){
// a 需要和上次枚举的数不同
if (first > 0 && nums[first] == nums[first - 1]){
continue;
}
int left = first + 1, right = n - 1;
while (left < right){
int sum = nums[first] + nums[left] + nums[right];
if (sum < target){
while (left < right && nums[left] == nums[++left]);
} else if (sum > target){
while (left < right && nums[right] == nums[--right]);
} else {
List<Integer> list = Arrays.stream(new int[] {nums[first], nums[left], nums[right]}).boxed().collect(Collectors.toList());
res.add(list);
while (left < right && nums[left] == nums[++left]);
while (left < right && nums[right] == nums[--right]);
}
}
}
return res;
}
}

浙公网安备 33010602011771号