问题:

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]
]

官方难度:

Medium

翻译:

给定一个长度为n的无序数组S,是否存在三个元素a,b,c,使得a+b+c=0?找出所有的可能解。

注意解集中不能存在重复解。

例子:

数组S:{-1,0,1,2,-1,-4}。

解集:[ [-1,0,1],[-1,-1,2] ]

方法一:

  1. 这是No.001(2Sum)问题的深入讨论。
  2. 在2Sum问题中,使用了哈希表的策略,但是有一个前提:2Sum问题中的数组是无序的。在无序数组中使用哈希表能大大提高效率,这是基于给2Sum问题的数组执行时间复杂度为O(logn)排序操作并不值得,但是3Sum问题没有这个顾虑,优先执行排序操作可以极大简化算法的性能。
  3. 由于要求解集不能存在重复解,所以要在思考到各种可能性。这里提一句:想要使用Set集合的“去重”特性取巧的行为,并行不通。因为在Set集合中存放类似数组或是集合,只要不是指向同一个地址,否则哪怕数据完全一样,都不会触发“去重”的特性。如下例子的输出是:2。
  4.     public static void main(String[] args) {
    Set<List<Integer>> result = new HashSet<>();
    List<Integer> list1 = new ArrayList<>();
    list1.add(1);
    List<Integer> list2 = new ArrayList<>();
    list1.add(1);
    result.add(list1);
    result.add(list1);
    result.add(list2);
    System.out.println(result.size());
    }
  5. 集合有List.contains()方法,判断集合中是否存在某个元素;而在数组中,也有Arrays.binarySearch()方法,可以在已经排序过的数组中达到这个目的,方法的返回值是寻找元素在数组中的下标索引,如果数组中没有这项,会返回一个负值(实际上是,如果存在这个数,在数组中的位置的相反数-1,如在数组[1,2,4]中查找3,数组[1,2,3,4]中元素3的索引2,所以方法的返回值是-2-1=-3)。这个方法的原理,是二分查找法,有较高的效率。
  6. 基本思想如下:从数组首位开始循环,直到数组的倒数第三项,将问题转化为,在剩下的已排序数组中,寻找第一个数的相反数的2Sum问题。这个2Sum问题的解集,可能有多个。
  7. 从数组剩余项的第一位开始循环,在接下来的数组中寻找指定值:前两个数的相反数。使用Arrays.binarySearch()方法,但是这个方法的入参中的数组长度要尽可能的缩小:从第j+1项开始,到lengthEnd结束。其中lengthEnd的值在开始的时候是数组最后一项的下标索引值,但是,如果在内循环中,出现了一次匹配之后,会更新这个值lengthEnd=index。因为在nums[j]越来越大的情况下,在前一次匹配中获得的index,其之后的值比nums[index]更大,没有考虑的必要。
  8. 注意得到的3个值,在数组中的下标依次是i,j,j+1+index。而且一定会有:nums[i]<=nums[j]<=nums[j+1+index]。
  9. 思考在Set集合不能去重的情况下,怎么保证解集没有重复?无论在外循环还是内循环中,分别维护上一个值previous,如果当前值等于previous,直接continue跳出本次循环。
  10. 有没有特殊情况,在循环进行到某一时刻,再也没有实现目标的机会,我可以直接跳出循环?答案是有的。在外循环中,如果当前项nums[i]>0,那么剩余两项比nums[i]更大,自然不可能达到三数之和为0的目标。同理,在内循环中,如果当前项nums[j]>-nums[i],也可以直接break。
  11. 最后考虑一点,返回值的类型被规定为List<List<Integer>>,用List的哪一个实现类比较好呢?鉴于整个方法,只要用到List.add()方法,不需要去读取数据,那么基于链表实现的LinkedList是否比基于数组实现的ArrayList,有更好的效率呢?

方法一的解题代码:

     private static List<List<Integer>> method(int[] nums) {
List<List<Integer>> result = new LinkedList<>();
// 先给数组排序
Arrays.sort(nums);
int previous = 1;
for (int i = 0; i < nums.length - 2; i++) {
// 第一个数大于0,之后更加没机会了
if (nums[i] > 0) {
return result;
}
// 如果和上个值相同,直接跳过
if (nums[i] == previous) {
continue;
}
// 下次探寻终点
int lengthEnd = nums.length;
int previous2 = Integer.MIN_VALUE;
for (int j = i + 1; j < lengthEnd - 1; j++) {
// 内循环第一个数,要大于0-array[i]
if (nums[j] > -nums[i]) {
break;
}
if (nums[j] == previous2) {
continue;
} else {
previous2 = nums[j];
}
// 定下两个,其实下一个也就决定了
int numberToFind = -(nums[i] + nums[j]);
// Arrays.binarySearch()方法只能在有序数组中使用
// 注意不是在整个数组上用这个方法,是在剩余数组上(夹逼过的)
int[] remian = Arrays.copyOfRange(nums, j + 1, lengthEnd);
int index = Arrays.binarySearch(remian, numberToFind);
// Arrays.binarySearch()查找失败时,会返回一个负值
// JAVA_API:这保证了当且仅当此键被找到时,返回的值将 >= 0。
if (index >= 0) {
// 因为有序,下一个array[j]一定比现在的大或等于(有去重要求)
// 而index之后的肯定比array[index]大,再之后的就没有必要遍历了
// 注意+1,因为lengthEnd是指长度而不是下标
// 再注意,这里的index,是去掉原数组前j项的index,加回去
lengthEnd = index + 1 + j;
// 这里可以保证array[i]<=array[j]<=array[index],不必担心Set集合接收重复问题
List<Integer> list = new LinkedList<>();
list.add(nums[i]);
list.add(nums[j]);
list.add(nums[index + j + 1]);
result.add(list);
}
}
previous = nums[i];
}
return result;
}

method

方法二:

  1. 如果在内循环中,只有一个解,我承认使用Arrays.binarySearch()的效率会很高,但实际上并不然。内循环中,寻找数组中2个值的和为给定值,这个问题的解可能是多个的,虽然我已经极力缩小二分查找的数组范围,但显然,应该存在更优的方法。
  2. 在内循环中,从数组两端开始考虑,根据nums[left]+nums[right]和-nums[i]的大小比较,不断地增加left,或是减小right。这种不断夹逼的方式,和循环使用二分查找相比,显然前者效率更高。
  3. 其余的关键点,方法一中都有提及,唯一不同的是,这次内循环中需要维护preLeft和preRight两个值。
  4. 在一次偶然的情况下,我将内循环判断结束的条件:nums[left]>-nums[i]中的执行语句,写成了return result,而不是break。惊讶地发现,结果竟然是正确的,这引发了我的思考,因为如此一来,外循环中判断nums[i]>0的条件,其实是可以省略的。以一个很大的数组为例,当外循环的第一项,执行到接近于0的负数时,内循环整体的趋势是right-1,left的值是不会变的,当left增加到大于-nums[i]的情况,那么即使nums[i]之后还有负数,也没有3数之和为0的可能性了。

方法二的解题代码:

     public static List<List<Integer>> threeSum(int[] nums) {
if (nums == null || nums.length < ) {
throw new IllegalArgumentException("Input error");
}
List<List<Integer>> result = new LinkedList<>();
Arrays.sort(nums);
int previous = ;
for (int i = ; i < nums.length - ; i++) {
if (nums[i] == previous) {
continue;
} else {
previous = nums[i];
}
int left = i + ;
int right = nums.length - ;
int numberToFind = -nums[i];
// 维护上一个左值和右值
int preLeft = Integer.MIN_VALUE, preRight = Integer.MIN_VALUE;
// 夹逼剩余两数之和
while (left < right) {
if (nums[left] > -nums[i]) {
return result;
}
if (nums[left] == preLeft) {
left++;
continue;
}
if (nums[right] == preRight) {
right--;
continue;
}
int sum = nums[left] + nums[right];
// 找到不能退出循环,还有其他可能性
if (sum == numberToFind) {
List<Integer> list = new LinkedList<>();
list.add(nums[i]);
list.add(nums[left]);
list.add(nums[right]);
result.add(list);
}
if (sum < numberToFind) {
preLeft = nums[left];
left++;
} else {
preRight = nums[right];
right--;
}
}
}
return result;
}

threeSum

相关链接:

https://leetcode.com/problems/3sum/

https://github.com/Gerrard-Feng/LeetCode/blob/master/LeetCode/src/com/gerrard/algorithm/medium/Q015.java

PS:如有不正确或提高效率的方法,欢迎留言,谢谢!

No.015:3Sum的更多相关文章

  1. No.016:3Sum Closest

    问题: Given an array S of n integers, find three integers in S such that the sum is closest to a given ...

  2. LeetCode第[16]题(Java):3Sum Closest 标签:Array

    题目难度:Medium 题目: Given an array S of n integers, find three integers in S such that the sum is closes ...

  3. leetcode笔记:3Sum Closest

    一.题目描写叙述 二.解题技巧 该题与3Sum的要求类似.不同的是要求选出的组合的和与目标值target最接近而不一定相等.但实际上,与3Sum的算法流程思路类似,先是进行排序.然后顺序选择数组A中的 ...

  4. LeetCode第[16]题(Java):3Sum Closest (和目标值最接近的三个数的和)——Medium

    题目难度:Medium 题目: Given an array S of n integers, find three integers in S such that the sum is closes ...

  5. LeetCode OJ:3Sum Closest(最接近的三数之和)

    Given an array S of n integers, find three integers in S such that the sum is closest to a given num ...

  6. Python练习题 015:一颗自由落地的球

    [Python练习题 015] 一球从100米高度自由落下,每次落地后反跳回原高度的一半,再落下.求它在第10次落地时,共经过多少米?第10次反弹多高? ----------------------- ...

  7. [LeetCode][Python]15:3Sum

    # -*- coding: utf8 -*-'''__author__ = 'dabay.wang@gmail.com' 15: 3Sumhttps://oj.leetcode.com/problem ...

  8. LeetCode第[15]题(Java):3Sum 标签:Array

    题目难度:Medium 题目: Given an array S of n integers, are there elements a, b, c in S such that a + b + c  ...

  9. ASP.NET—015:ASP.NET中无刷新页面实现

    原文作者:杨友山 原文地址:http://blog.csdn.net/yysyangyangyangshan/article/details/39679823 前面也说过在asp.net中前后前交互的 ...

随机推荐

  1. Atitit 混合叠加俩张图片的处理 图像处理解决方案 javafx blend

    Atitit 混合叠加俩张图片的处理 图像处理解决方案 javafx blend 1.1. Jhlabs 好像有了可以叠加算法,但是没有找到doc1 1.2. 自己算法叠加1 1.3. 使用javaf ...

  2. Atitti.数字证书体系cer pfx attilax总结

    Atitti.数字证书体系cer pfx attilax总结 一.数字证书常见标准 1 数字证书文件格式(cer和pfx)的区别: 1 二.数字证书存储内容 2 X.509是一种非常通用的证书格式. ...

  3. WPF仿Win7便笺

    最近用WPF写了个仿WIN7下面的便笺小工具,还算是比较华丽的,相似度99%以上吧,在集成了便笺原有的功能以外,当然也做了略微的修改,比如加了关于作者版本信息和修改了原有删除便笺的系统弹出框.软件开机 ...

  4. 每天一个linux命令(30): chown命令

    chown将指定文件的拥有者改为指定的用户或组,用户可以是用户名或者用户ID:组可以是组名或者组ID:文件是以空格分开的要改变权限的文件列表,支持通配符.系统管理员经常使用chown命令,在将文件拷贝 ...

  5. ROC曲线与AUC值

    本文根据以下文章整理而成,链接: (1)http://blog.csdn.net/ice110956/article/details/20288239 (2)http://blog.csdn.net/ ...

  6. 编译原理简单语法分析器(first,follow,分析表)源码下载

    编译原理(简单语法分析器下载) http://files.cnblogs.com/files/hujunzheng/%E5%8A%A0%E5%85%A5%E5%90%8C%E6%AD%A5%E7%AC ...

  7. java中Object.equals()简单用法

    /* equals()方法默认的比较两个对象的引用! */ class Child { int num; public Child(int x){ num = x; } //人文的抛出运行时异常的好处 ...

  8. 构建自己的PHP框架--搭建基本结构

    首先,我们来说一下,为什么要创建自己的框架? 为什么要创建自己的框架呢?如果你跟周围的人讨论,每个人都会告诉你重复发明轮子是一件糟糕的事情,你最好选择一个已有的框架,忘掉“创建自己的框架”这种想法.大 ...

  9. [algorithm] My rookie plan to start

    若干年后,经验有一些,但根基不牢靠.[algorithm] series 借助学习Standard Template Library: Algorithms的这段时期,在自己的算法和c++基础方面加些 ...

  10. 硬刚Google ,这家小公司的增长团队长啥样

    背景: AdRoll 是一家主打重定向广告(Retargeting)服务的技术公司,基于用户浏览记录等信息,为广告主提供几乎瞬时的广告位购买服务,当前估值15.5亿美元.吊打谷歌, AdRoll 已经 ...