题目
给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
思路
三重for循环超时,排除。先排序。找到第一个非负数的位置。这三个元素有三种情况:0个负数,3个非负数(0);1个负数,2个非负数;2个负数,1个非负数。前两个数用for循环遍历,第三个数使用map的containsKey方法找到,空间代价替代时间代价。注意:不能用list,list.contains(xxx)时间复杂度O(n),而map.containsKey(xxx)时间复杂度小于O(n),尽量使用map。
代码
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> res=new ArrayList<List<Integer>>();
if(nums.length<=2){
return res;
}
Arrays.sort(nums);
// 找到第一个非负数的位置
int zeroindex=-1;
for(int i=0;i<nums.length;i++){
if(nums[i]==0){
zeroindex=i;
break;
}
if(nums[i]>0&&i==0){
zeroindex=i;
break;
}
if(nums[i]>0&&nums[i-1]<0){
zeroindex=i;
break;
}
}
// 第一个非负数的位置一些特殊情况
if(zeroindex==-1){
return res;
}
if(zeroindex==0&&nums[0]>0){
return res;
}
if(zeroindex==0&&nums[0]==0){
if(nums[1]==0&&nums[2]==0){
List<Integer> list=new ArrayList<Integer>();
list.add(0);
list.add(0);
list.add(0);
res.add(list);
return res;
}else{
return res;
}
}
// 0个负数,3个非负数(0)
if(zeroindex<nums.length-2){
if(nums[zeroindex]==0&&nums[zeroindex+1]==0&&nums[zeroindex+2]==0){
List<Integer> list=new ArrayList<Integer>();
list.add(0);
list.add(0);
list.add(0);
if(!res.contains(list)){
res.add(list);
}
}
}
// Map以空间换时间,containsKey方法时间复杂度小于O(n),找第三个数不用for循环而使用map降低时间复杂度
Map<String,Integer> map_after0=new HashMap<String,Integer>();
int repeatNum=1;
for(int i=zeroindex;i<nums.length;i++){
if(i!=zeroindex&&nums[i-1]==nums[i]){
repeatNum++;
}else{
repeatNum=1;
}
map_after0.put(""+nums[i]+"-"+repeatNum,nums[i]);
}
// 1个负数,2个非负数
for(int i=0;i<zeroindex;i++){
// 重复,跳过
if(i!=0&&nums[i-1]==nums[i]){
continue;
}
if(zeroindex==nums.length-1){
break;
}
for(int j=zeroindex;j<nums.length-1;j++){
// 重复,跳过
if(j!=zeroindex&&nums[j-1]==nums[j]){
continue;
}
// 减少j的一半冗余时间
if(((float)nums[i])*(-0.5)<nums[j]){
break;
}
int expect=nums[i]*(-1)-nums[j];
if(nums[j]==expect){
if(nums[j+1]==expect){
List<Integer> list=new ArrayList<Integer>();
list.add(nums[i]);
list.add(nums[j]);
list.add(expect);
// list.contains时间复杂度O(n),不用
// if(!res.contains(list)){
// res.add(list);
// }
res.add(list);
}else{
}
}else{
// 找第三个数不用for循环而使用map的containsKey降低时间复杂度
if(map_after0.containsKey(""+expect+"-"+"1")){
List<Integer> list=new ArrayList<Integer>();
list.add(nums[i]);
list.add(nums[j]);
list.add(expect);
// list.contains时间复杂度O(n),不用
// if(!res.contains(list)){
// res.add(list);
// }
res.add(list);
}else{
}
}
}
}
// 2个负数,1个非负数
if(zeroindex==1){
return res;
}else{
// Map以空间换时间,containsKey方法时间复杂度小于O(n),找第三个数不用for循环而使用map降低时间复杂度
Map<String,Integer> map_before0=new HashMap<String,Integer>();
int repeatNum2=1;
for(int i=0;i<zeroindex;i++){
if(i!=0&&nums[i-1]==nums[i]){
repeatNum2++;
}else{
repeatNum2=1;
}
map_before0.put(""+nums[i]+"-"+repeatNum2,nums[i]);
}
// 2个负数,1个非负数
for(int k=zeroindex;k<nums.length;k++){
// 重复,跳过
if(k!=zeroindex&&nums[k-1]==nums[k]){
continue;
}
for(int j=zeroindex-1;j>0;j--){
// 重复,跳过
if(j!=zeroindex-1&&nums[j+1]==nums[j]){
continue;
}
// 减少j的一半冗余时间
if(((float)nums[k])*(0.5)<nums[j]*(-1)){
break;
}
int expect=nums[k]*(-1)-nums[j];
if(nums[j]==expect){
if(nums[j-1]==expect){
List<Integer> list=new ArrayList<Integer>();
list.add(expect);
list.add(nums[j]);
list.add(nums[k]);
// list.contains时间复杂度O(n),不用
// if(!res.contains(list)){
// res.add(list);
// }
res.add(list);
}else{
}
}else{
// 找第三个数不用for循环而使用map的containsKey降低时间复杂度
if(map_before0.containsKey(""+expect+"-"+"1")){
List<Integer> list=new ArrayList<Integer>();
list.add(expect);
list.add(nums[j]);
list.add(nums[k]);
// list.contains时间复杂度O(n),不用
// if(!res.contains(list)){
// res.add(list);
// }
res.add(list);
}
}
}
}
}
return res;
}
}
其他思路:
左右指针。http://www.cnblogs.com/liuliu5151/p/9124596.html
注意
1.Map以空间换时间,containsKey方法时间复杂度小于O(n),找第三个数不用for循环而使用map的containsKey方法降低时间复杂度。
2.注意list.contains(xxx)占用的时间复杂度O(n),不用。
3.左右指针较快。