题目描述:
给定一个包含 n 个整数的数组 nums
,判断 nums
中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4], 满足要求的三元组集合为: [ [-1, 0, 1], [-1, -1, 2] ]
看到这道题就能想到我做过的第一道题:两数之和。根据两数之和的经验,这道题有三种思路。
两数之和博客:https://blog.csdn.net/weixin_37850160/article/details/86500847
第一种思路:暴力for循环,直接三层for嵌套,判断输出即可,这个是最简便最容易想的的问题,但时间复杂度太高O(n^3),很容易超时,所以不推荐使用这种方法。
第二种思路:就是用对撞指针法,这种方法前提是先排序,然后遍历一层循环,让除了一个元素的其他元素进行对撞指针,定义一个首指针和一个尾指针,然后判断,a+b+c=0,得到想要结果,重点 在于去重,就是前后两个元素重复了,已经比较过了就跳过,继续下一趟循环。
第三种思路:就是用查找表,用map存储元素和其对应的下标,a+b+c=0,a+c=-c,利用这个思路来进行遍历判断,判断c是否在map中,在map中时就证明 找到了可以时这个满足加起来等于0的条件。重点还是在去重,和上面一样去重的思路。
具体代码:
第一种方法就不写代码了,所有人应该都会。这种结果提交也是会超时。
第二种方法:
第二种方法,对撞指针法(先排序),时间复杂度O(n^2),遍历数组,让除了第二个元素的其它元素,定义两个指针,一前一后,进行判断。
Arrays.sort (a);//重点必须先排序
List<List<Integer>> list2 = new ArrayList <> ( );
for(int i = 0;i<a.length-2;i++) {
// 除了每次第一个元素外,其余元素进行对撞指针的判断(切记,首指针要小于尾指针)
int k=a.length - 1;
// 去重,当后一个元素和前一个元素相同时,就将i++,跳过重复的元素
if (i != 0 && a[i] == a[i - 1]) {
continue;
}
for (int j=i + 1; j < k; ) {
// 加起来大于0,就将末尾的指针向前移动
if (a[j] + a[k] > -a[i]) {
k--;
// 小于0,将前面的指针向后移动
} else if (a[j] + a[k] < -a[i]) {
j++;
} else {
// 等于0得到结果,加入到list中
List <Integer> list=new ArrayList <> ( );
list.add ( a[i] );
list.add ( a[j] );
list.add ( a[k] );
list2.add ( list );
// 去重,前后两个元素相同则,++跳过
while (j < k && a[i] == a[j + 1]) j++;
// 去重,前后两个元素相同则,--跳过
while (j < k && a[k] == a[k - 1]) k--;
// 去重复,添加到大列表中
// if (!list2.contains ( list )){
// list2.add ( list );
// }
// 去重
j++;
k--;
}
}
}
System.out.println(list2);
执行结果:
执行用时:
第三种方法:
第三种方法,查找表(有一个存数据的地方,便于查找),map,set等等
Arrays.sort ( a );
Map<Integer,Integer> map = new HashMap <> ( );
Set<List<Integer>> set = new HashSet <> ( );
List<List<Integer>> lists = new ArrayList <> ( );
// 先将所有元素添加到mpa中
for (int i= 0;i<a.length;i++){
map.put ( a[i],i );
}
for (int i=0;i<a.length;i++){
if (i!=0&&a[i] == a[i-1]){
continue;
}
for (int j =i+1;j<a.length;j++){
// 判断a+b+c=0---->c=-(a+b),c是否在map中,并且同一个元素不能颠倒使用
int rel = -a[i]-a[j];
if (map.containsKey (rel)&&map.get ( rel)>j&&map.get ( rel)>i){
List<Integer> list = new ArrayList <> ( );
list.add ( a[i] );
list.add ( a[j] );
list.add ( rel);
while (j<a.length-1&&a[j]==a[j+1]) j++;
// 去重,用set,可以添加到set中的就方到list中
// if (set.add ( list )){
lists.add ( list );
// }
}
}
}
System.out.println(lists);
执行结果:
执行用时:
总结:暴力循环不推荐,推荐双指针和查找表,时间复杂度O(n^2),这道题两数之和特别像,只要抓住两数之和,沿着这个思路,将三数,四数,都转化为两数之和求解判断。
2019-3-21