题目
回溯
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Main {
public static void main(String[] args) {
Main main = new Main();
int [] height = new int[] {-1, 0, 1, 2, -1, 4};
main.threeSum(height);
}
int [] nums;
List<List<Integer>> ans = new ArrayList<>();
Map<Integer, Integer> numUseMap = new HashMap<>();
public List<List<Integer>> threeSum(int[] nums) {
this.nums = nums;
for (int i = 0; i < nums.length; i ++) {
numUseMap.put(nums[i], numUseMap.getOrDefault(nums[i], 0) + 1);
}
huisu(new ArrayList<>(), 0);
return ans;
}
public void huisu(List<Integer> path, int tartget) {
// 终止条件
if (path.size() > 3) {
return;
}
if (path.size() == 3) {
if (tartget == 0) {
ans.add(new ArrayList<>(path));
}
return;
}
for (int i = 0; i < nums.length; i ++) {
int count = numUseMap.get(nums[i]);
if (count > 0) {
// 进行选择
numUseMap.put(nums[i], count - 1);
path.add(nums[i]);
huisu(path, tartget + nums[i]);
// 撤销选择
path.remove(path.size() - 1);
numUseMap.put(nums[i], count);
}
}
}
}
基本思路
- 通过hashmap记录每个元素可使用的次数, 利用回溯法进行全遍历
缺点
- 最后的结果出现大量的元素相同的list, 去重的成本非常大, 因此需要考虑如何在遍历的过程中完成去重
三重循环
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
Main main = new Main();
int [] height = new int[] {0, 0, 0, 0};
main.threeSum(height);
}
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> ans = new ArrayList<>();
if (nums.length < 3) {
return ans;
}
// 首先对数组排序
Arrays.sort(nums);
// 利用三个指针 i left right
// 寻找-nums[i] = nums[left] + nums[right]的情况
Integer preI = null;
for (int i = 0; i < nums.length; i ++) {
if (nums[i] > 0) {
// 如果nums[i] > 0, 那么left和right对应的数字肯定>0
break;
}
if (preI == null) {
preI = nums[i];
} else if (nums[i] == preI) {
// 如果当前数字和之前的数字相同, 跳过当前数字
continue;
}
int left = i + 1;
Integer preLeft = null;
while (left < nums.length) {
if (preLeft == null) {
preLeft = nums[left];
} else if (nums[left] == preLeft) {
// 如果当前数字和之前的数字相同, 跳过当前数字
left++;
continue;
}
if (nums[i] > 0 && nums[left] > 0) {
left ++;
break;
}
int right = left + 1;
Integer preRight = null;
while (right < nums.length) {
if (preRight == null) {
preRight = nums[right];
} else if (nums[right] == preRight) {
// 如果当前数字和之前的数字相同, 跳过当前数字
right ++;
continue;
}
if (nums[i] + nums[left] > 0 && nums[right] > 0) {
right ++;
break;
}
if (nums[i] + nums[left] + nums[right] == 0) {
List<Integer> temp = new ArrayList<>();
temp.add(nums[i]);
temp.add(nums[left]);
temp.add(nums[right]);
ans.add(temp);
}
preRight = nums[right];
right ++;
}
preLeft = nums[left];
left ++;
}
preI = nums[i];
}
return ans;
}
}
基本思路
-
利用i, left, right表示数组中的三个元素, 通过对数组排序, 以及记录i, left, right上一个元素值的方式, 每个位置如果遇见重复的元素, 就跳过. 通过有序数组 + 相同元素跳过的方式解决了答案重复性的问题
-
因为是有序的, 所以比较每个位置当前值和目标值是可以对遍历截断的, 减少计算次数
缺点
- 本质依旧是三重遍历, 最优几个用例会超时
循环 + 双指针
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Main {
public static void main(String[] args) {
Main main = new Main();
int [] height = new int[] {-1, 0, 1, 2, -1, 4};
main.threeSum(height);
}
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> ans = new ArrayList<>();
if (nums.length < 3) {
return ans;
}
// 首先对数组排序
Arrays.sort(nums);
// 利用三个指针 i left right
// 寻找nums[i] + nums[left] + nums[right] == 0的情况
for (int i = 0; i < nums.length; i ++) {
if (nums[i] > 0) {
// 如果nums[i] > 0, 那么left和right对应的数字肯定>0
break;
}
if ((i - 1) >= 0 && nums[i] == nums[i - 1]) {
// 遇见重复元素跳过
continue;
}
// 左右指针互相配合
int left = i + 1;
int rightLimit = nums.length - 1;
while (left < nums.length) {
if ((left - i) > 1 && nums[left] == nums[left - 1]) {
// 遇见重复元素跳过
left ++;
continue;
}
if (nums[i] + nums[left] > 0) {
break;
}
// 右指针从数组右侧向左,
int right = rightLimit;
while (right > left) {
if ((right + 1) < nums.length && nums[right] == nums[right + 1]) {
// 遇见重复元素跳过
right --;
continue;
}
if (nums[i] + nums[left] + nums[right] < 0) {
break;
}
if (nums[i] + nums[left] + nums[right] == 0) {
// 因为下一次当left增加时, 可能的right只会出现在当前right的左边,相当于缩小了下一次的范围
rightLimit = right;
List<Integer> temp = new ArrayList<>();
temp.add(nums[i]);
temp.add(nums[left]);
temp.add(nums[right]);
ans.add(temp);
}
right --;
}
left ++;
}
}
return ans;
}
}
基本思路
-
发现left指针的变化可能会缩短right指针的变化范围, 因此将该因素考虑. 例如当a1 + b1 + c1 = 0 成立, 此时b1增加成b2, c2可能的范围一定是小于c1的
-
注意每重循环里的条件, 及时对循环进行终止, 光用==0不行, 必须有如下的条件:
if (nums[i] + nums[left] > 0) {
break;
}
if (nums[i] + nums[left] + nums[right] < 0) {
break;
}