问题:
Given an array S of n integers, are there elements a, b, c in S such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.
Note: The solution set must not contain duplicate triplets.
For example, given array S = [-1, 0, 1, 2, -1, -4],
A solution set is:
[
[-1, 0, 1],
[-1, -1, 2]
]
题目的要求是:
从给定的数组中找到不重复的组合。
每个组合包含3个元素,其和为0。
代码示例:
1、暴力解法
public class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
if (nums == null || nums.length < 3 ) {
return result;
}
int len = nums.length;
//现将给定数组从小到大排序
Arrays.sort(nums);
//最小值大于0或最大值小于0,显然是无法找到需要的组合
if (nums[0] > 0 || nums[len-1] < 0) {
return result;
}
//考虑到组合的和为0,因此整个组合不可能全部为负数或正数
//用于存储负数,及负数出现的次数
HashMap<Integer, Integer> negativeMap = new HashMap<>();
ArrayList<Integer> negativeList = new ArrayList<>();
//用于存储正数,及正数出现的次数
HashMap<Integer, Integer> positiveMap = new HashMap<>();
ArrayList<Integer> positiveList = new ArrayList<>();
//存储是否出现0
int numOfZero = 0;
for (int num : nums) {
if (num < 0) {
addNumToCollections(num, negativeMap, negativeList);
} else if (num > 0) {
addNumToCollections(num, positiveMap, positiveList);
} else {
++numOfZero;
}
}
//出现3个以上的0,显然0 0 0的组合满足条件
if (numOfZero >= 3) {
List<Integer> answer = new ArrayList<>();
for (int i = 0; i < 3; ++i) {
answer.add(0);
}
result.add(answer);
}
//每次先取一个负数,再取一个正数
for (int i = 0; i < negativeList.size(); ++i) {
for (int j = 0; j < positiveList.size(); ++j) {
int negative = negativeList.get(i);
int positive = positiveList.get(j);
//求出需要的第3个数
int complete = 0 - negative - positive;
boolean addFlag = false;
//需要的数小于0,并且存在
if (complete < 0 && negativeMap.containsKey(complete)) {
//需要的数与主动取出的负数不一致时,它的下标必须大于主动取得负数的下标,否则会出现重复的组合
if ((complete != negative && negativeList.indexOf(complete) > i)||
//需要的数与主动取出的负数一致,那么要求这个负数至少出现两次以上
(complete == negative && negativeMap.get(negative) > 1)) {
addFlag = true;
}
//所需的数为正数,类似
} else if (complete > 0 && positiveMap.containsKey(complete)){
if ((complete != positive && positiveList.indexOf(complete) > j)
|| (complete == positive && positiveMap.get(positive) > 1)) {
addFlag = true;
}
} else if (complete == 0 && numOfZero > 0){
addFlag = true;
}
if (addFlag) {
addAnswerToResult(negative, positive, complete, result);
}
}
}
return result;
}
private void addNumToCollections(int num, Map<Integer, Integer> map, List<Integer> list) {
if (map.containsKey(num)) {
map.replace(num, map.get(num), map.get(num)+1);
} else {
map.put(num, 1);
list.add(num);
}
}
private void addAnswerToResult(int negative, int positive, int complement, List<List<Integer>> result) {
List<Integer> answer = new ArrayList<>();
answer.add(negative);
answer.add(positive);
answer.add(complement);
result.add(answer);
}
}
这个方法可以得到正确结果,但是会超时。整个算法仅看for循环似乎复杂度是O( N2 ),但算上Map的查询,复杂度可能接近O( N3 )。
2、类似于求最大水位的问题
个人记录-LeetCode 11. Container With Most Water
public class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
if (nums == null || nums.length < 3) {
return result;
}
int len = nums.length;
Arrays.sort(nums);
if (nums[0] > 0 || nums[len-1] < 0) {
return result;
}
//轮询数组中的每一个数字,为了避免重复
//找到在这个数字之后,出现的另外两个数字,构成组合
for (int i = 0; i < len-2;) {
int firstIndex = i + 1;
int lastIndex = len - 1;
while (firstIndex < lastIndex) {
int curr = nums[firstIndex] + nums[lastIndex] + nums[i];
//类似于求水位问题,当前的和大于0,降低高位的下标
if (curr > 0) {
do {
--lastIndex;
//跳过重复的值
} while (firstIndex < lastIndex && nums[lastIndex] == nums[lastIndex+1]);
//当前的和小于0,增加低位的下标
} else if (curr < 0){
do {
++firstIndex;
} while(firstIndex < lastIndex && nums[firstIndex] == nums[firstIndex-1]);
} else {
addAnswerToResult(nums[firstIndex], nums[lastIndex], nums[i], result);
//等于0时,找出新的两个数,与当前数字构成组合
//低位增加、高位减小才有可能满足条件
do {
++firstIndex;
}while(firstIndex < lastIndex && nums[firstIndex] == nums[firstIndex-1]);
do {
--lastIndex;
}while (firstIndex < lastIndex && nums[lastIndex] == nums[lastIndex+1]);
}
}
//跳过重复的组合
do{
++i;
}while(i < nums.length && nums[i] == nums[i-1]);
}
return result;
}
private static void addAnswerToResult(int first, int last, int complement, List<List<Integer>> result) {
List<Integer> answer = new ArrayList<>();
answer.add(first);
answer.add(last);
answer.add(complement);
result.add(answer);
}
}
这个方法的复杂度是O( N2 )。