原题重述:(点击图片可以进入来源链接)

这到题目的中文解释是,

输入一个数组,例如{-1 0 1 2 -1 -4},从数组中找三个数(a,b,c),使得其和0,输出所有的(a,b,c)组合。

要求abc不能重复,并且a<=b<=c。

拿到这个题目的时候,其实每个程序猿都能想到如下的算法,也就是暴力破解,其时间复杂度为o(n^3):

             for(int i=0;i<nums.length;i++){
for(int j=i+1;j<nums.length;j++){
for(int k=j+1;k<nums.length;j++){
if(nums[i]+nums[j]+nums[k]==0){
addResult(nums[i], nums[j], nums[k]);
}
}
}
}

首先需要对输入的数组进行排序,这样的话由于上面的i<j<k,所以可以保证nums[i]<nums[j]<nums[k]。

其实我的算法的思路就是在暴力破解的基础上进行优化,尽量降低时间复杂度。

在java中对数组排序的方法是:Arrays.sort(nums);

第三个循环其实是没有必要的,因为在确定了i,j的值之后,需要寻找的nums[k]的值就已经确定了,即-(nums[i]+nums[j])。

因此无需循环,只需要判断数组剩下的元素中是否存在这个值就可以了。

基于这个思路我构建了一个hashmap作为hash索引表,用于查找每个值出现的位置:(考虑到一个值可能出现在多个位置的情况,用arraylist)

因为nums是已经排序过的,所以索引表中的arraylist也是已排序好的。

HashMap<Integer, ArrayList<Integer>> index = new HashMap<>();

构建这个索引表的代码如下:

         for(int i=0;i<nums.length;i++){
int num = nums[i];
if(num==0){
n++;
}
if(index.get(num)==null){ index.put(num, new ArrayList<Integer>());
} index.get(num).add(i); }

这里面的n是表示0的个数,如果n>=3,就直接输出一个[0 0 0]了。

从索引表查询需要的数的方式,我想了很久,最后想到一个很不错的方法:

                 int p = -(nums[i]+nums[j]);
if(p<0) continue;
ArrayList<Integer> in = index.get(p);
if(in==null) continue;
if(in.get(in.size()-1)>j){
if(p>nums[j]){
addResult(nums[i], nums[j],p);
}else if(p>nums[i]){
addResult(nums[i], p,nums[j]);
}else{
addResult(p,nums[i], nums[j]);
} }

第2行,为什么要舍弃p<0的情况?因为要避免重复。如果p也是负数的话,由于nums[i]<nums[j]那么会出现两种情况:

①nums[i]和nums[j]都是正数;

②nums[i]是负数,nums[j]是正数 。

那么在其他的扫描过程中一定会出现:

①那时的nums[i]'=p,p'=nums[i],nums[j]'=nums[j];

②那时的p'=nums[j],nums[i]'=min(nums[i],p),nums[j]'=max(nums[i],p)。

第5行in.get(in.size()-1)>j是什麼意思?

我们这个时候是需要找一个k(k>j),使得nums[k]=p,如果有就输出nums[k]。

ArrayList in表示使得nums[k]=p的所有k值,如果最大的k值大于j,那不就表示存在k>j,使得nums[k]=p了吗?

一定要求k>j,因为如下的情况是不符合要求的:

输入 [-1 0 1 2] 不能输出 [-1 -1 2] 因为-1的索引是[0],在遍历时它不满足k>j

关于避免重复的问题,

我用addResult函数来避免重复,大家一看应该就懂

     HashSet<String> repeat = new HashSet<String>(); // 查重
List<List<Integer>> result = new LinkedList<List<Integer>>(); public void addResult(int n1, int n2, int n3) {
String s = n1 + "&"+n2; if (!repeat.contains(s)) {
List<Integer> p = new ArrayList<>();
p.add(n1);
p.add(n2);
p.add(n3);
result.add(p);
repeat.add(s);
}
}

最终详细的代码如下:

 import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List; /**
* 优化了的o(n^2) 3sum算法
* @author user
*
*/
public class Solution {
HashSet<String> repeat = new HashSet<String>(); // 查重
List<List<Integer>> result = new LinkedList<List<Integer>>(); public void addResult(int n1, int n2, int n3) {
String s = n1 + "&"+n2; if (!repeat.contains(s)) {
List<Integer> p = new ArrayList<>();
p.add(n1);
p.add(n2);
p.add(n3);
result.add(p);
repeat.add(s);
}
} public List<List<Integer>> threeSum(int[] nums) {
if (nums.length < 3) {
return result;
}
Arrays.sort(nums);
if (nums.length == 3) {
if (nums[0] + nums[1] + nums[2] == 0) { addResult(nums[0], nums[1], nums[2]);
return result;
} else {
return result;
}
}
HashMap<Integer, ArrayList<Integer>> index = new HashMap<>();
int n=0; for(int i=0;i<nums.length;i++){
int num = nums[i];
if(num==0){
n++;
}
if(index.get(num)==null){ index.put(num, new ArrayList<Integer>());
} index.get(num).add(i); }
if(n>=3) addResult(0, 0, 0); for(int i=0;i<nums.length;i++){
if((nums[i]<0&&nums[nums.length-1]<-i)||(nums[i]>0&&nums[0]>-nums[i])) continue; for(int j=i+1;j<nums.length;j++){ int p = -(nums[i]+nums[j]); if(p<0) continue;
ArrayList<Integer> in = index.get(p);
if(in==null) continue;
if(in.get(in.size()-1)>j){
if(p>nums[j]){
addResult(nums[i], nums[j],p);
}else if(p>nums[i]){
addResult(nums[i], p,nums[j]);
}else{
addResult(p,nums[i], nums[j]);
} } } } return result;
} public static void main(String[] args) {
long m = System.currentTimeMillis();
int a[] = {-1,-2,-3,4,1,3,0,3,-2,1,-2,2,-1,1,-5,4,-3}; Solution solution = new Solution();
System.out.println(solution.threeSum(a).size()); long n = System.currentTimeMillis();
System.out.println(n - m); } }

3Sum algorithm - 非常容易理解的实现 (java)的更多相关文章

  1. Atitit  深入理解命名空间namespace  java c# php js

    Atitit  深入理解命名空间namespace  java c# php js 1.1. Namespace还是package1 1.2. import同时解决了令人头疼的include1 1.3 ...

  2. 理解和解决Java并发修改异常ConcurrentModificationException(转载)

    原文地址:https://www.jianshu.com/p/f3f6b12330c1 理解和解决Java并发修改异常ConcurrentModificationException 不知读者在Java ...

  3. 深入理解和探究Java类加载机制

    深入理解和探究Java类加载机制---- 1.java.lang.ClassLoader类介绍 java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字 ...

  4. 深入理解什么是Java泛型?泛型怎么使用?【纯转】

    本篇文章给大家带来的内容是介绍深入理解什么是Java泛型?泛型怎么使用?有一定的参考价值,有需要的朋友可以参考一下,希望对你们有所助. 一.什么是泛型 “泛型” 意味着编写的代码可以被不同类型的对象所 ...

  5. [转载] 深入理解Android之Java虚拟机Dalvik

    本文转载自: http://blog.csdn.net/innost/article/details/50377905 一.背景 这个选题很大,但并不是一开始就有这么高大上的追求.最初之时,只是源于对 ...

  6. 如何理解和使用Java package包

    Java中的一个包就是一个类库单元,包内包含有一组类,它们在单一的名称空间之下被组织在了一起.这个名称空间就是包名.可以使用import关键字来导入一个包.例如使用import java.util.* ...

  7. 深入理解JVM(6)——Java内存模型和线程

    Java虚拟机规范中定义了Java内存模型(Java Memory Model,JMM)用来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果(“即Ja ...

  8. 理解JVM之Java内存区域

    Java虚拟机运行时数据区分为以下几个部分: 方法区.虚拟机栈.本地方法栈.堆.程序计数器.如下图所示: 一.程序计数器 程序计数器可看作当前线程所执行的字节码行号指示器,字节码解释器工作时就是通过改 ...

  9. 平衡二叉树(AVL)的理解和实现(Java)

    AVL的定义 平衡二叉树:是一种特殊的二叉排序树,其中每一个节点的左子树和右子树的高度差至多等于1.从平衡二叉树的名字中可以看出来,它是一种高度平衡的二叉排序树.那么什么叫做高度平衡呢?意思就是要么它 ...

随机推荐

  1. C++的性能C#的产能?! - .Net Native 系列《三》:.NET Native部署测试方案及样例

    之前一文<c++的性能, c#的产能?!鱼和熊掌可以兼得,.NET NATIVE初窥> 获得很多朋友支持和鼓励,也更让我坚定做这项技术的推广者,希望能让更多的朋友了解这项技术,于是先从官方 ...

  2. 转职成为TypeScript程序员的参考手册

    写在前面 作者并没有任何可以作为背书的履历来证明自己写作这份手册的分量. 其内容大都来自于TypeScript官方资料或者搜索引擎获得,期间掺杂少量作者的私见,并会标明. 大部分内容来自于http:/ ...

  3. CI Weekly #7 | Instgram/Quora 等大公司如何做持续部署?

    终于,你们期待的 flow.ci iOS 项目持续集成 开始公测了.在这几个工作日, flow.ci 做了些许「功能优化」与「问题修复」,性能和体验都在持续优化中.比如: iOS 快速入门文档更新: ...

  4. 使用nwjs开发桌面应用之Hello,World!

    今天发现原来JavaScript也可以用来开发桌面应用程序,顿时有一种很牛逼的感觉,于是马上就开始了Hello,World!,感受一下JavaScript的强大. 可以用来开发桌面应用的js框架有三种 ...

  5. 游戏服务器菜鸟之C#初探三游戏服务

    在经过上述2番折腾之后,最后决定使用TCP进行通信,所以在一次进行重构 主要重构的要点 1.将过来的HTPP请求,重构为TCP请求: 2.使用组件FluenScheduler进行怪物的定时刷新,和定时 ...

  6. BootStrap_04之jQuery插件(导航、轮播)、以及Less

    1.列偏移与列排序: ①列偏移:控制列出现的位置,某列偏移后,后序列会随之偏移--只能右偏移: col-lg/md/sm/xs-offset-*; ②列排序:控制某一列的位置,该列可以左(pull)右 ...

  7. UML类图与面向对象设计原则

    1. 引言     从大一开始学习编程,到如今也已经有两年了.从最初学习的Html,Js,JaveSe,再到JavaEE,Android,自己也能写一些玩具.学习过程中也无意识的了解了一些所谓的设计模 ...

  8. C可变参数的函数

    我们实现一个简单的printf函数(可变参数) #include <stdio.h> #include <stdarg.h> void myprintf(const char ...

  9. Hibernate(6)—— 一对多 和 多对多关联关系映射(xml和注解)总结

    俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及的知识点总结如下: One to Many 映射关系 多对一单向外键关联(XML/Annotation) 一对多单向外键关联(XM ...

  10. 手动制作微信h5分享活动页面

    现在网上有很多自动制作h5宣传页的网站,可以通过传图,点几下鼠标就可以制作一个集动画.生产二维码等各种功能于一身的h5微信宣传页.对于运营来讲,非常方便,没有技术门槛,不足之处就是只有特定的动画效果, ...