No.015:3Sum
问题:
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] ]
方法一:
- 这是No.001(2Sum)问题的深入讨论。
- 在2Sum问题中,使用了哈希表的策略,但是有一个前提:2Sum问题中的数组是无序的。在无序数组中使用哈希表能大大提高效率,这是基于给2Sum问题的数组执行时间复杂度为O(logn)排序操作并不值得,但是3Sum问题没有这个顾虑,优先执行排序操作可以极大简化算法的性能。
- 由于要求解集不能存在重复解,所以要在思考到各种可能性。这里提一句:想要使用Set集合的“去重”特性取巧的行为,并行不通。因为在Set集合中存放类似数组或是集合,只要不是指向同一个地址,否则哪怕数据完全一样,都不会触发“去重”的特性。如下例子的输出是:2。
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());
}- 集合有List.contains()方法,判断集合中是否存在某个元素;而在数组中,也有Arrays.binarySearch()方法,可以在已经排序过的数组中达到这个目的,方法的返回值是寻找元素在数组中的下标索引,如果数组中没有这项,会返回一个负值(实际上是,如果存在这个数,在数组中的位置的相反数-1,如在数组[1,2,4]中查找3,数组[1,2,3,4]中元素3的索引2,所以方法的返回值是-2-1=-3)。这个方法的原理,是二分查找法,有较高的效率。
- 基本思想如下:从数组首位开始循环,直到数组的倒数第三项,将问题转化为,在剩下的已排序数组中,寻找第一个数的相反数的2Sum问题。这个2Sum问题的解集,可能有多个。
- 从数组剩余项的第一位开始循环,在接下来的数组中寻找指定值:前两个数的相反数。使用Arrays.binarySearch()方法,但是这个方法的入参中的数组长度要尽可能的缩小:从第j+1项开始,到lengthEnd结束。其中lengthEnd的值在开始的时候是数组最后一项的下标索引值,但是,如果在内循环中,出现了一次匹配之后,会更新这个值lengthEnd=index。因为在nums[j]越来越大的情况下,在前一次匹配中获得的index,其之后的值比nums[index]更大,没有考虑的必要。
- 注意得到的3个值,在数组中的下标依次是i,j,j+1+index。而且一定会有:nums[i]<=nums[j]<=nums[j+1+index]。
- 思考在Set集合不能去重的情况下,怎么保证解集没有重复?无论在外循环还是内循环中,分别维护上一个值previous,如果当前值等于previous,直接continue跳出本次循环。
- 有没有特殊情况,在循环进行到某一时刻,再也没有实现目标的机会,我可以直接跳出循环?答案是有的。在外循环中,如果当前项nums[i]>0,那么剩余两项比nums[i]更大,自然不可能达到三数之和为0的目标。同理,在内循环中,如果当前项nums[j]>-nums[i],也可以直接break。
- 最后考虑一点,返回值的类型被规定为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
方法二:
- 如果在内循环中,只有一个解,我承认使用Arrays.binarySearch()的效率会很高,但实际上并不然。内循环中,寻找数组中2个值的和为给定值,这个问题的解可能是多个的,虽然我已经极力缩小二分查找的数组范围,但显然,应该存在更优的方法。
- 在内循环中,从数组两端开始考虑,根据nums[left]+nums[right]和-nums[i]的大小比较,不断地增加left,或是减小right。这种不断夹逼的方式,和循环使用二分查找相比,显然前者效率更高。
- 其余的关键点,方法一中都有提及,唯一不同的是,这次内循环中需要维护preLeft和preRight两个值。
- 在一次偶然的情况下,我将内循环判断结束的条件: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/
PS:如有不正确或提高效率的方法,欢迎留言,谢谢!
No.015:3Sum的更多相关文章
- 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 ...
- 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 ...
- leetcode笔记:3Sum Closest
一.题目描写叙述 二.解题技巧 该题与3Sum的要求类似.不同的是要求选出的组合的和与目标值target最接近而不一定相等.但实际上,与3Sum的算法流程思路类似,先是进行排序.然后顺序选择数组A中的 ...
- 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 ...
- 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 ...
- Python练习题 015:一颗自由落地的球
[Python练习题 015] 一球从100米高度自由落下,每次落地后反跳回原高度的一半,再落下.求它在第10次落地时,共经过多少米?第10次反弹多高? ----------------------- ...
- [LeetCode][Python]15:3Sum
# -*- coding: utf8 -*-'''__author__ = 'dabay.wang@gmail.com' 15: 3Sumhttps://oj.leetcode.com/problem ...
- 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 ...
- ASP.NET—015:ASP.NET中无刷新页面实现
原文作者:杨友山 原文地址:http://blog.csdn.net/yysyangyangyangshan/article/details/39679823 前面也说过在asp.net中前后前交互的 ...
随机推荐
- vue for 绑定事件
vue for 绑定事件 <div id="pro_list" v-for="item in pro_list"> <div class=&q ...
- Java EE开发平台随手记1
过完春节以来,一直在负责搭建公司的新Java EE开发平台,所谓新平台,其实并不是什么新技术,不过是将目前业界较为流行的框架整合在一起,做一些简单的封装和扩展,让开发人员更加易用. 和之前负责具体的项 ...
- javaweb回顾第十一篇过滤器(附实现中文乱码问题)
1:过滤器概念 过滤器就是一种在请求目标资源的中间组件,比喻把污水转换成纯净水中间需要一个污水净化设备,那么这个设备就好比一个过滤器.那么我用图来表示过滤器(可以有多个过滤器)运行的过程 2:Filt ...
- 深入理解HTML表格
前面的话 在CSS出现之前,table元素常常用来布局.这种做法在HTML4之后不再推荐使用.而现在有些矫枉过正,使用table展示数据都可能会被说不规范.本文将详细介绍HTML表格table tab ...
- poj1949Chores(建图或者dp)
/* 题意:n个任务,有某些任务要在一些任务之前完成才能开始做! 第k个任务的约束只能是1...k-1个任务!问最终需要最少的时间完成全部的 任务! 思路:第i个任务要在第j个任务之前做,就在i,j之 ...
- Js杂谈-DOM
前言 对jQuery的依赖.导致js的原生方法的淡忘,如果是封装自己的库,那势必要用到js的许多原生方法.从Jquery强大的dom处理开始,我们开始回顾javascript那些古老而坚挺的DOM方法 ...
- Java多线程系列--“基础篇”11之 生产消费者问题
概要 本章,会对“生产/消费者问题”进行讨论.涉及到的内容包括:1. 生产/消费者模型2. 生产/消费者实现 转载请注明出处:http://www.cnblogs.com/skywang12345/p ...
- Android中自定义属性(attrs.xml,TypedArray的使用)
做Android布局是件很享受的事,这得益于他良好的xml方式.使用xml可以快速有效的为软件定义界面.可是有时候我们总感觉官方定义的一些基本组件不够用,自定义组件就不可避免了.那么如何才能做到像官方 ...
- Windows Azure Cloud Service (10) Role的生命周期
<Windows Azure Platform 系列文章目录> 在上一章内容中,我们提到了Windows Azure会依次调用角色(Role)实例的OnStart()方法和Run()方法. ...
- 30天C#基础巩固------了解委托,string练习
---->了解委托. 生活中的例子:我要打官司,我需要找一个律师,法庭上面律师为当事人辩护,它真正执行的是当事人的陈词,这时律师 就相当于一个委托对象.当事人则委托律师为自己辩解. ...