您的位置:首页 > 其它

[LeetCode] 3Sum

2015-09-18 17:59 288 查看
sum3问题:

对于给定的一组int,找到其中的三个数,这三个数相加为0。比如:

输入:

int[] nums= {-1 0 1 2 -1 -4}

输出:

(-1, 0, 1)

(-1, -1, 2)

第一个思路,最简单明了的,三循环暴力求解。。。时间复杂度当然是不敢恭维。。。

public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> list = new ArrayList<List<Integer>>();
int length = nums.length;
for(int i =0; i < length -2; i++){
int a = nums[i];
for(int j = i+1; j <length-1 ; j++){
int b = nums[j];
for(int k = j+1; k< length; k++){
int c = nums[k];
if(a+b+c == 0){
ArrayList<Integer> item = new ArrayList<Integer>();
item.add(a);
item.add(b);
item.add(c);
Collections.sort(item);
if(!list.contains(item)){
list.add(item);
}
}
}
}
}
return list;


如果我们先进行排序,然后前面两个数循环,第三个数去二分查找,就可以得到一个O(n2*logn)的算法。

考虑下加法的特征,我们还可以稍微简化一下。

三个数相加等于0,则第一个数必然小于0,最后一个数必然大于0。

所以,如果我们取头尾的数,然后在中间进行二分查找,则还存在三个数学性质,可以简化判断。

第一个数大于0

最后一个数小于0

中间数小于第一个数或者大于最后一个数。

这样可以在同阶的情况下,稍微减少点循环数。

//先排序,复杂度O(nlogn)
Arrays.sort(nums);
int length = nums.length;
List<List<Integer>> list = new ArrayList<List<Integer>>();

//两遍循环,进行查找。复杂度(n2*logn)
for(int i=0; i<length; i++){
int a = nums[i];
//第一个数大于0
if(a > 0){
break;
}
for(int j=length-1; j>1;j--){
int c = nums[j];
//第三个数小于0
if(c < 0){
break;
}
int b = -(a+c);
//中间的数最小或者最大了
if(b < a || b > c){
break;
}
if(contains(nums, b, i+1, j-1)){
ArrayList<Integer> item = new ArrayList<Integer>();
item.add(a);
item.add(b);
item.add(c);
if(!list.contains(item)){
list.add(item);
}
}
}
}
return list;

//二分查找
public boolean contains(int[] nums, int a, int low, int high){
while(low <= high) {
int middle = (low + high)/2;
if(a == nums[middle]) {
return true;
}else if(a <nums[middle]) {
high = middle - 1;
}else {
low = middle + 1;
}
}
return false;
}


反向思考,我们既然能先固定两头去查找中间的数,当然也能先固定中间的数,再去查找两头。尤其在计算了sum是否大于0后,我们可以确定遍历的方向。因此不需要漫无目的的遍历(0至i)*(i至length)了,而只需要遍历(0至length)即可。因此最后这个复杂度就下降到了i循环的O(n)以及查找low,high两者加一起的O(n)。时间复杂度为O(n2)。

//先排序,复杂度O(nlogn)
Arrays.sort(nums);
int length = nums.length;
List<List<Integer>> list = new ArrayList<List<Integer>>();

//复杂度O(n2)
int low,high,sum;
for (int i = 1; i < length - 1; i++) {
low = 0;
high = length - 1;
while (low < i && high > i) {
sum = nums[i] + nums[low] + nums[high];
if (sum == 0) {
ArrayList<Integer> item = new ArrayList<Integer>();
item.add(nums[low]);
item.add(nums[i]);
item.add(nums[high]);
if (!list.contains(item)) {
list.add(item);
}
high--;
low++;
} else if (sum > 0) {
high--;
} else {
low++;
}
}
}
return list;


对于2sum, 3sum closest, 4sum这三个问题,解题思路也都是一样的。

暴力查找最简单但复杂度最高。

利用排序,对最后一个数进行二分查找,可以把一个n降低到logn。

利用排序,对最后两个数进行加加减减的遍历,则可以由于方向的确定,把复杂度降低一个n阶。

最后附上一个4sum的代码实现:

public class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
int length = nums.length;
List<List<Integer>> list = new ArrayList<List<Integer>>();
if(length < 4){
return list;
}

//排序
Arrays.sort(nums);
for(int i=0;i <length-3;i++){
for(int j=i+1; j<length-2;j++){
//先双循环
int sum = nums[i]+nums[j]-target;
int m =j+1;
int n =length-1;
while(m<n){
//回到2sum的思路,按照result>或<0,进行++--
int result =nums[m]+nums
+sum;
if(result==0){
ArrayList<Integer> item = new ArrayList<Integer>();
item.add(nums[i]);
item.add(nums[j]);
item.add(nums[m]);
item.add(nums
);
if(!list.contains(item)){
list.add(item);
}
m++;
n--;
}else if(result >0){
n--;
}else{
m++;
}
}
}
}
return list;
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  leetcode