三数之和

最近在leetcode遇到这个题目,题目描述如下:

解决这个问题的话,我们可以借助两数之和的求解过程。两数之和的问题描述如下:

解决两数之和的话,可以使用一个hashMap,保存值与下标,类似与登记功能,比如说2这个元素来了,要凑成9的话,还需要找到7这个元素,但是发现hashMap中没有7这个元素,于是2只好等待,将自己与自己的下标存到hashMap中,之后7来了,它需要找到值为2的元素,于是它去hashMap去找,发现有2这个元素,于是目的就达成了。

代码如下:

import java.util.*;
class Solution {
    public int[] twoSum(int[] nums, int target) {
        int length=nums.length;
        //map中存放元素和元素的索引
        Map<Integer,Integer>map=new HashMap<Integer,Integer>();
        int result[]=new int[2];
        for(int i=0;i<length;i++){
            int temp=target-nums[i];
            if(map.get(temp)!=null){
                result[0]=i;
                result[1]=map.get(temp);
                if(result[0]>result[1]){
                    int t=result[0];
                    result[0]=result[1];
                    result[1]=t;
                }
                break;
            }else{
                map.put(Integer.valueOf(nums[i]),Integer.valueOf(i));
            }
        }
        return result;
    }
}

下面继续说三数之和,受到两数之和的启发,我们可以将三数之和a+b+c=0,转换成b+c=-a

于是我们可以写出如下代码:

但是一点执行代码,会出现如下的提示:

可以看到,上述代码的输出结果是包含有重复结果的,且结果集中的顺序也不对。但是在上述代码中使用了Collection.sort()来对一个结果内部数据排过一次序,但是结果与结果之间的顺序是乱的,也就是说,上述代码面临着去重与结果集中结果A与结果B的顺序问题。那么如何解决这些问题呢。

我们先考虑解决结果与结果之间的顺序问题,比如上面的【-1,-1,2】应该在【-1,0,1】之前。那么思考这个问题产生的原因,可以发现,之所以会出现【-1,0,1】在【-1,-1,2】之前,是因为代码是直接从nums中顺序取元素的,且nums是未排序的,比如说题目中nums为【-1,0,1,2,-1,4】,上述代码会先对第一个-1从它后面找能够满足b+c=1的两个数,于是按照两数之和的思想,会先找到【-1,0,1】然后是【-1,-1,2】。若想使【-1,-1,2】到【-1,0,1】前面,我们首先要对nums排序

-4    -1    -1     0      1     2

然后我们就开始找吧,首先-4我们直接跳过,因为这对我们的分析不起作用,然后我们找到了-1,从第一个-1后面开始,这里好像是我们首先处理-1了,但是如果只是先对nums排序,而其余代码与之前一样的话,还是会出现一样的结果,因为当遍历到第二个-1时,hashMap中没有出现2,所以只会登记-1,这样-1就没有来得急被先处理,显然这是因为算法的问题,于是想到要改变算法,不再使用hashMap了,尽管它帮助我们解决了两数之和问题。为了确保后面的-1得到及时的处理,这里我们建立两个指针,第一个指向后面的-1,第二个指针指向nums末尾,这样做有两个好处,一是-1能够得到及时的处理,二是我们也愿意相信能使-1+c=1的数,有大概率出现在nums末尾,因为nums是有序的。

通过上面的改进,low所指向的-1能够及时的处理了。所以问题只剩下排重了。在上述的基础上,我们不难发现,如果对第一个-1找完之后,是会有【-1,-1,2】和【-1,0,1】这两个结果的,于是下标增加,指向第二个-1,如果继续对这个-1进行上述查找的话,又会得到一个重复的结果【-1,0,1】。这就出现重复的问题,可以发现,如果前后两个元素的值相等,那么应该直接跳过后面这个元素的查找。代码就可以改进为(还有一些细节处理没提及,但容易理解):

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> resultList=new ArrayList<List<Integer>>();
        Arrays.sort(nums);
        
        for(int i=0;i<nums.length-2;i++) {
            if(i>0&&nums[i]==nums[i-1]){
                continue;
            }
            int low=i+1;
            int high=nums.length-1;
            int mid=(0-nums[i])>>1;
            while(low<high&&nums[low]<=mid&&nums[high]>=mid){
                
                int temp=nums[low]+nums[i]+nums[high];
                if(temp==0){
                    resultList.add(Arrays.asList(nums[i],nums[low],nums[high]));
                    low++;
                    high--;
                    while(low<high&&nums[low-1]==nums[low]){
                        low++;
                    }
                    while(low<high&&nums[high+1]==nums[high]){
                        high--;
                    }
                }else if(temp>0){
                    high--;
                }else{
                    low++;
                }
            }
        }
        return resultList;
    }
}

上述代码中注意到有mid=(0-nums[i])>>1,显然mid是(0-a)的一半,因为nums是排过序的,而之后我们要满足b+c=-a,至少得满足b<=mid,c>=mid,如果b>mid,c也会大于mid(nums有序),这样b+c>2*mid,也就是b+c>a了。换句话说,b应该在a/2的左边,c应该在a/2的右边。我们还可以发现,如果nums[low]+nums[high]>mid时,这时应该high要右移,即high--,但是如果移动后的high与之前的high对应nums[high]相同的话,就不应该比较,因为如果相同的话,nums[low]+nums[新的high]还是会大于mid的,所以我们应向前找到一个high,这个high与之前high对应nums[high]不同。于是可以对上述代码进行如下优化

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> resultList=new ArrayList<List<Integer>>();
        Arrays.sort(nums);
        
        for(int i=0;i<nums.length-2;i++) {
            //排重
            if(i>0&&nums[i]==nums[i-1]){
                continue;
            }
            int low=i+1;
            int high=nums.length-1;
            int mid=(0-nums[i])>>1;
            while(low<high&&nums[low]<=mid&&nums[high]>=mid){
                
                int temp=nums[low]+nums[i]+nums[high];
                if(temp==0){
                    resultList.add(Arrays.asList(nums[i],nums[low],nums[high]));
                    low++;
                    //排重
                    while(low<high&&nums[low-1]==nums[low]){
                        low++;
                    }
                    high--;
                    //排重
                    while(low<high&&nums[high+1]==nums[high]){
                        high--;
                    }
                    if(nums[high]<mid){
                        break;
                    }
                }else if(temp>0){
                    high--;
                    //这里不是对正确结果排重
                    //移动后的nums[high]与移动前的nums[high]相同话,结果肯定是会不满足的
                    //所以要找到与之前不同的nums[high]
                    while(low<high&&nums[high+1]==nums[high]){
                        --high;
                    }
                }else{
                    low++;
                    //这里不是对正确结果排重
                    //移动后的nums[low]与移动前的nums[low]相同话,结果肯定是会不满足的
                    //所以要找到与之前不同的nums[low]
                    while(low<high&&nums[low]==nums[low-1]){
                        ++low;
                    }
                }
            }
        }
        return resultList;
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值