15. 三数之和(中等)

一、题意
给定一个包含 n 个整数的数组 S,是否存在属于 S 的三个元素 a,b,c 使得 a + b + c = 0 ?找出所有不重复的三个元素组合使三个数的和为零。

注意:结果不能包括重复的三个数的组合。

例如, 给定数组 S = [-1, 0, 1, 2, -1, -4],

一个结果集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
二、分析和解答
1、对两个数的和进行分析
之前使用的HashSet的空间占用时间解决的两个数的和,因为那个题返回的是两个数的下标,所以不能进行排序。
如果返回的值不是下标,而是数组中的值。
先对其进行排序(从左至右依次增大),再设置两个指针 i,j 分别指向数组两端:如果两指针所指向数的和大于target,j指针前移,找一个更小的数;如果两指针所指向数的和小于target,i指针后移,找一个更大点的数;如果相同,则返回即可。
2、三数的和
对两个数的和的扩展。对每个数遍历,求两个数的和为0 - nums[k]即可。代码如下:

 public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> res = new ArrayList();
        Arrays.sort(nums);
        for(int k=0;k<nums.length;k++){
            int i = k+1,j = nums.length - 1;//***
            while(i < j){              
                int value = nums[i] + nums[j];
                if(value == (-nums[k])){
                    List<Integer> list = new ArrayList();
                    list.add(nums[k]);
                    list.add(nums[i]);
                    list.add(nums[j]);
                    res.add(list);
                    //重值处理
                    //****一定要有i < j条件,否则i会到最后
                    while(i<j && nums[i] == nums[i+1]){
                        i++;
                    }
                    //****一定要有i < j条件,否则j会取-1
                    while(i<j && nums[j] == nums[j-1]){
                        j--;
                    }
                    //***
                    i++;
                    j--;
                }
                else if(value < (-nums[k]))
                    i++;
                else
                    j--;
            }
            //****重值处理
            while(k < nums.length-1 && nums[k] == nums[k+1]){
                k++;
            }
        }
        return res;
    }

虽然思想比较简单,但是还是有很多很多难点:
(1)i 的取值。这是一个难点:0 - nums[k] 为我们所求的target,即两个数的和为target,这两个数必须是k值之后的两个位置,而不可能处于k位置之前。
a)设nums=[a1,a2,a3,a4…an],当第一个数为a1时,和为0-a1的第二三个数的位置处于【a2–an】之间,设为ax和ay,那么有ax + ay = 0 - a1
b)那么,当第一个数为a2的时候,target为0 - a2,如果存在一个在a2之前的数为第二个数为a1,假设第三个数为ax,也就是说a1 + ax = 0 - a2,可以换为a2 + ax = 0 - a1
因此,这是b)中如果去当前值前面位置的数,和a)的情况就发生重合。

(2)重值问题。有两个级别的重值进行处理,以[-1,0,1,2,-1,-4]为例,进行排序后为[-4,-1,-1,0,1,2],如果是比较原始的代码,不进行重值处理的时候,代码如下:

  public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> res = new ArrayList();
        Arrays.sort(nums);
        for(int k=0;k<nums.length;k++){        
            int i = k+1,j = nums.length - 1;
            while(i < j){                                 
                int value = nums[i] + nums[j];
                if(value == (-nums[k])){
                    List<Integer> list = new ArrayList();
                    list.add(nums[k]);
                    list.add(nums[i]);
                    list.add(nums[j]);
                    res.add(list);

                    i++;//暂且这么写
                    j--;
                }
                else if(value < (-nums[k]))
                    i++;
                else
                    j--;
            }

        }
        return res;
    }
输出:[[-1,-1,2],[-1,0,1],[-1,0,1]]
预期:[[-1,-1,2],[-1,0,1]]

会发现多了一个[-1,0,1]。

a)所以第一个是在k层次(外面的那层循环)上进行去重:[-4,-1,-1,0,1,2] 第二个位置为-1,可以得到这个答案,但第三个位置也是-1,也可以得到这个答案。这种去重想法很简单:只要判断nums[i]和它的前一位或者后一位是否相同即可,若相同就跳过!但是关键是把这种判断放在哪里?
我一开始把它放在了for循环的下面,即在得到三个合适的数之前进行判断,输出:[[-1,0,1]] 。。。预期:[[-1,-1,2],[-1,0,1]],会发现,少了一个[-1,-1,2],当时我就蒙了。怎么回事呢?其实这样使得两个相同的数-1(位置k),-1(位置k+1)不能同时出现在解中,只要出现重复的数,就直接跳过,显然是错误的,不合题意的!
其实就是去重的时机不合适!应将其放在while(i < j)结束的后面(等把一个合适的三个取值加入list列表中在进行判断)。这样只是去除了第一个数取相同数(-1)的情况,无论第二三个数取什么。符合题意!
注意:这里的去重是while,而不是if!
根本原因还是逻辑与代码表达的逻辑一致。
b) 第二个级别的去重。上面第k位置去重之后,结果如下:
输入:[-2,0,0,2,2]
输出:[[-2,0,2],[-2,0,2]]
预期:[[-2,0,2]]
。当i指向0,j指向2 的时候,符合题意,开始的时候这里处理的逻辑并不是i++,j–;如何处理我也没找到方法。后来发现,这个是这样的:如果i值不变,j值前移,那么此时如果j仍指向2,重复,如果小于2了,那此时肯定不成立;同样,若j值不变,i值后移,若i仍指向0,虽符合计算,但重复,若不指向0,肯定会比0大,不符合计算!因此,只需要让i后移到一个不为0的值,让j前移到不为2的值即可。如果该位和下一位数相同,跳过!

共同点:先加入list一个成立的值,再去重。否则,可能会影响list中的结果。

C++代码:

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> res;
        sort(nums.begin(),nums.end());

        for(int k=0;k<nums.size();k++){
            int i = k + 1;
            int j = nums.size() - 1;
            while(i < j){
                int value = nums[i] + nums[j];
                if(value > (0-nums[k])){
                    j--;
                }else if(value < 0-nums[k]){
                    i++;
                }else{
                    vector<int> list;
                    list.push_back(nums[k]);
                    list.push_back(nums[i]);
                    list.push_back(nums[j]);
                    res.push_back(list);
                    while(i<j && nums[i] == nums[i+1]){
                        i++;
                    }
                    while(i<j && nums[j] == nums[j-1]){
                        j--;
                    }
                    i++;
                    j--;
                }                
            }

            while(k<nums.size()-1 && nums[k] == nums[k+1]){
                    k++;
            }
        }
        return res;
    }
};
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值