暴力破解O(n³)不做说明
leetcode也过不去
改进后的暴力破解
本人一番改进,最后能过去了,但是速度感人
public List<List<Integer>> threeSum1(int[] nums) {
//数组排序
Arrays.sort(nums);
int len = nums.length;
int top = len - 1;
Set<List<Integer>> list = new HashSet<List<Integer>>();
for (int i = 0; i < len - 2; i++) {
//此处进入正数区域,直接跳出
if (nums[i] > 0) {
// System.out.println(nums[i] + ">0,进入正数区域,退出");
break;
}
top = len - 1;
for (int j = i + 1; j < len - 1; j++) {
int two = nums[i] + nums[j];
//因为从小往大排序,因此当前俩数需要的正数在j之前,跳出
if (-two < nums[j]) {
// System.out.println(two + "的相反数已经小于"+nums[j]+",不可能变为0,退出");
break;
}
for (int k = top; k > j; k--) {
//两个负数过于小找不到相应的大数,跳出
if (-two > nums[k]) {
// System.out.println(two+"相反数已经大于当前"+nums[k]+",退出");
break;
}
if (two + nums[k] == 0) {
List<Integer> li = new ArrayList<Integer>(3);
li.add(nums[i]);
li.add(nums[j]);
li.add(nums[k]);
//通过排序可以使set筛选出去
li.sort(Comparator.comparingInt(Integer::intValue));
list.add(li);
}
top = k;
}
}
}
return new ArrayList<List<Integer>>(list);
}
看完题解之后的双指针方法
不得不说算法这个东西 真的是牛逼
public List<List<Integer>> threeSum(int[] nums) {
//数组排序
Arrays.sort(nums);
int len = nums.length;
int k = 0;
List<List<Integer>> list = new LinkedList<List<Integer>>();
for (; k < len - 1; k++) {
// 进行到正数部分,不可能等于0
if (nums[k] > 0) {
break;
}
//已经用该数查找过,跳过
if (k > 0 && nums[k] == nums[k - 1]) {
continue;
}
int i = k + 1;
int j = len - 1;
while (i < j) {
int tmp = nums[i] + nums[k] + nums[j];
if (tmp == 0) {
ArrayList<Integer> li = new ArrayList<Integer>(3);
li.add(nums[i]);
li.add(nums[j]);
li.add(nums[k]);
list.add(li);
//去除重复结果
while (i < j && nums[i] == nums[i + 1]) {
i++;
}
while (i < j && nums[j] == nums[j - 1]) {
j--;
}
}
if (tmp > 0) {
j--;
} else {
i++;
}
}
}
return list;
}
查看最快速度的代码
确实挺快,不过有个缺点就很明显,map数组的容量是个硬伤。类似于桶排序的缺点,而且更甚,取得是最大值与最小值的差。
public List<List<Integer>> threeSum(int[] nums) {
// 数组长度小于3,不可能成功
if (nums.length < 3) {
return Collections.emptyList();
}
List<List<Integer>> res = new ArrayList<>();
int minValue = Integer.MAX_VALUE;
int maxValue = Integer.MIN_VALUE;
int negSize = 0;
int posSize = 0;
int zeroSize = 0;
// 统计最大值,最小值,含有0的数量,正数数量,负数数量
for (int v : nums) {
if (v < minValue) {
minValue = v;
}
if (v > maxValue) {
maxValue = v;
}
if (v > 0) {
posSize++;
} else if (v < 0) {
negSize++;
} else {
zeroSize++;
}
}
// 含0大于等于3,添加[0,0,0]的情况
if (zeroSize >= 3) {
res.add(Arrays.asList(0, 0, 0));
}
// 正数或者负数有一方不存在,那么就不存在成功情况
if (negSize == 0 || posSize == 0) {
return res;
}
// 去掉无用的大值和小值
if (minValue * 2 + maxValue > 0) {
maxValue = -minValue * 2;
} else if (maxValue * 2 + minValue < 0) {
minValue = -maxValue * 2;
}
int[] map = new int[maxValue - minValue + 1];
int[] negs = new int[negSize];
int[] poses = new int[posSize];
negSize = 0;
posSize = 0;
for (int v : nums) {
if (v >= minValue && v <= maxValue) {
// 类似与桶排序存储于map,每个数只在正负数数组添加一次,存贮本数与最小数的差值,确保大于等于0
if (map[v - minValue]++ == 0) {
if (v > 0) {
poses[posSize++] = v;
} else if (v < 0) {
negs[negSize++] = v;
}
}
}
}
// 对正负数分别排序
Arrays.sort(poses, 0, posSize);
Arrays.sort(negs, 0, negSize);
int basej = 0;
/**
* 从最大的负数开始遍历 依次寻找一个大于本次负数相反数一半的正数 如果0-负数-正数的差值处于该负数与该正数之间,那么就找到了一组
* 因为正数负数是两个边界,差值必须在两个数中间,所以该正数至少要大于等于该负数的相反数的一半
* 例如[-2,-1,3]的情况,第一次选用-1和3作为边界的时候,-2并不会被判定为一组,因为-2不在-1与3之间
* 假如本次寻找判定为成功了一组为[-1,3,-2],那么下次以-2和3为边界的时候,查到-1即[-2,3,-1],结果重复
* 因此从中间不断向外展开,确保不会重复
*/
// 对负数部分进行遍历,从最大的负数开始
for (int i = negSize - 1; i >= 0; i--) {
int nv = negs[i];
// 查找时先确定两边,然后再中间寻找符合条件的,因为正数部分至少大于等于负数相反数的一半
int minp = (-nv) >>> 1;
// 查找第一个大于负数相反数一半的正数
while (basej < posSize && poses[basej] < minp) {
basej++;
}
// 对正数部分进行遍历
for (int j = basej; j < posSize; j++) {
int pv = poses[j];
// 获取除当前两个数之外的差值
int cv = 0 - nv - pv;
// 该数必须在,两个边界之间,确保不会重复
if (cv >= nv && cv <= pv) {
// 差值与负数相同,即两个负数的和与正数互为相反数
if (cv == nv) {
// 判断是否存在两个或以上这样的数
if (map[nv - minValue] > 1) {
res.add(Arrays.asList(nv, nv, pv));
}
}
// 差值与正数数相同,即两个正数的和与负数互为相反数
else if (cv == pv) {
if (map[pv - minValue] > 1) {
res.add(Arrays.asList(nv, pv, pv));
}
}
// 正常情况
else {
if (map[cv - minValue] > 0) {
res.add(Arrays.asList(nv, cv, pv));
}
}
} else if (cv < nv) {
//差值已经比当前负数小了,而正数还在增大,绝无可能再成功了
break;
}
}
}
return res;
}