1、 题目描述
LeetCode 15.三数之和
😫给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
2、思路
思路历程
-
简单的思路,三重for循环,分别是num1、num2、num3 暴力枚举所有的数组元素,求和后与0比对 相等则加入最后的返回值中,最后对返回值去重,简单粗暴可以得出结果,但是leetcode最终会执行超时。
-
优化思路,暴力穷举的问题有哪些:
-
重复的值最终收集一次就行了,穷举会有多余的计算。
-
穷举会完全遍历每一个元素 ,时间复杂度达到了 n3 ,既然是规则的计算 是否存在逻辑 可以不进行遍历所有的元素,只需要遍历部分元素计算即可
解决暴力穷举的方案
重复的值最终收集一次就行了,穷举会有多余的计算 解决方案
首先对原数组进行排序(排序后很好定位num1、num2、num3的值 便于去重),num1选择一个元素后穷举排序后的num2与num3,得到此时 num1 值的所有三数之和为0 的num2与num3。
后续遍历时 如果选择的num1以前选择过 那么这时num1对应的num2与num3肯定是重复的,可以跳过遍历。
是否存在逻辑 可以不进行遍历所有的元素
针对排序后的数组,选择确定 num1 后,可以利用双指针的逻辑 ,num2 从 num1 后一个元素向右遍历,num3 从数组末尾向左遍历。
-
如果 num1 大于0,由于排序后的num1之后的数肯定大于0 ,那么可以肯定 在此之后再也没有能等于 零的三数之和了可以直接返回。
-
如果此时 三数之和大于0,那么将 num3 向左移 减小 num3
-
如果此时 三数之和小于0,那么将 num2 向右移 增大 num2。
-
如果此时 三数之和等于0,收集。
3、代码
为了不啰里八嗦 😂,只提供优化后的思路代码
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
// 排序数组 方便去重
Arrays.sort(nums);
// 结果
List<List<Integer>> res = new ArrayList<>();
// 第一个数索引
for (int num1Index = 0; num1Index < nums.length - 2; num1Index++) {
int num1 = nums[num1Index];
// 此后不存在三数之和等于0
if(num1 > 0) {
return res;
}
// 去重 num1
if (num1Index > 0 && num1 == nums[num1Index - 1]) {
continue;
}
// 分别从左边取一个数与右边取一个数
int num2Index = num1Index + 1;
int num3Index = nums.length - 1;
while (num2Index < num3Index) {
int threeNumSum = num1 + nums[num2Index] + nums[num3Index];
if (threeNumSum == 0) {
List<Integer> resArr = new ArrayList<>();
resArr.add(num1);
resArr.add(nums[num2Index]);
resArr.add(nums[num3Index]);
res.add(resArr);
// 去重 num2
while (num2Index < num3Index && nums[num2Index] == nums[num2Index + 1]) {
num2Index++;
}
// 去重 num3
while (num2Index < num3Index && nums[num3Index] == nums[num3Index - 1]) {
num3Index--;
}
num2Index++;
num3Index--;
} else if (threeNumSum < 0) {
num2Index++;
} else {
num3Index--;
}
}
}
return res;
}
}
4、算法分析
复杂度分析
时间复杂度: num1 遍历一遍,num2与num3 共用遍历一遍 ,总时间复杂度为 O(n2)
空间复杂度: 数组排序算法O(nlogn),修改了输入的原始数组 可以当成使用了数组的副本 O(n),总空间复杂度O(n)
实际执行耗时与内存占用率:
5、总结
-
暴力穷举能解决问题,但是一般总是可以优化的,优化的点无非就是暴力的缺点,分析出暴力的缺点就可以针对性的提出改进的思路。
-
没有好的解题思路 先暴力做出来,再优化也是不错的。
6、tips
上述实现的代码中 ,内存占用偏高,还有优化空间,我自己也实验了近三十个版本。最有效的提升空间利用率到方式是 将 new ArrayList 对象改为使用 Arrays.asList ,执行效率如图:
那么问题又来了 ,new ArrayList 与 Arrays.asList 的区别是什么呢?