leetcode_15_三数之和

题目:

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。

注意:答案中不可以包含重复的三元组。

 

示例:

给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/3sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。


思路:问题是求三个数之和,可以考虑转换为两个数之和从而化繁为简。而两个数之和通常是使用双指针的方法解决。

①先将整个数组预处理:递增排序。便于遍历。

②将三个数之和的问题转化为两个数之和的问题:

先固定一个x位置,这样就成了求两个数之和:nums[y] + nums[z] = -nums[x]的问题。

这样就可以使用双指针的算法来解决:

x = 0;  //左指针
z = lens - 1;   //右指针
y = x + 1;   //中间的遍历指针

虽然这样转化后可以简化问题,但是复杂度高的问题仍然没有解决:O(n^3)。

降低复杂度策略:

① 

如果nums[x] > 0,而nums数组是已经排序的,则此时很显然,不可能存在y,z,使得nums[x] + nums[y] + nums[z] = 0

所以此时直接break结束运行即可。

②去重。

如:有数组nums = {-1,-1,0,1}。

此时如果不合理去重,则会有两个相同的结果:[-1,0,1]、[-1,0,1]。

产生这样结果的原因在于:相邻两个数相等。

找到原因就好解决:

int vx = nums[x];
if(vx > 0) break;
// 下面语句有两个作用:
//①去除重复的x。比如:[-1,-1,0,1]。此时如果不加上下面的判断,
// 则会有[-1,0,1]、[-1,0,1]两个重复的答案。
// ②降低复杂度。这个很明显。
if(x>0 && nums[x-1]==vx){
   x++;
   continue;
}

如上所示,可以通过判断:nums[x-1]与nums[x]的值是否相等来去重。

这只是外层循环固定位置x的去重。

内层两个指针:y与z的去重方法一样,不再赘述,详情见代码。。。233333


实现:

JAVA版:

package leetcode;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/*
USER:LQY
DATE:2020/7/27
TIME:9:03
*/
public class leetcode_15 {
    public static void main(String []args){
        int []nums = {-1, 0, 1, 2, -1, -4};
        List<List<Integer>> ans = new leetcode_15().threeSum(nums);
        for(List l : ans){
            System.out.println(l);
        }
    }

    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);  //先排序
        List<List<Integer>> ans = new ArrayList<>();
        int lens = nums.length;
        int x = 0;  //左指针
        int z = lens - 1;   //右指针
        int y;   //中间的遍历指针
        // 采用双指针的做法。
        // 虽然是求三个数之和。
        // 但是可以先固定一个x位置,这样就成了求两个数之和 nums[y]+nums[z] == -nums[x]的问题。
        // 注意。必须使用去重的if判断语句来降低复杂度,同时,
        // 如果固定的第一个数nums[x] > 0,而由于现在nums数组是排好序的,此时
        // 想要三个数的和等于0,肯定是不可能的,直接break结束即可。这样进一步降低复杂度。
        while(x<=z-2 && x<lens-2){

            int vx = nums[x];
            if(vx > 0) break;
            // 下面语句有两个作用:
            //①去除重复的x。比如:[-1,-1,0,1]。此时如果不加上下面的判断,
            // 则会有[-1,0,1]、[-1,0,1]两个重复的答案。
            // ②降低复杂度。这个很明显。
            if(x>0 && nums[x-1]==vx){
                x++;
                continue;
            }
            // System.out.println("start at "+x+","+z);
            y = x + 1;
            int tz = z;
//            int flag = 0;
            while(y<tz && tz<lens){
                int vy = nums[y];
                int vz = nums[tz];
                // if(((tz<lens-1&&nums[tz+1]==vz)){
                //     tz--;
                //     continue;
                // }
                // int v = vx + nums[y] + nums[tz];
                if(vy+vz+vx == 0){
                    //此时满足条件
                    // System.out.println("fit at :"+x+","+tz);
                    ans.add(Arrays.asList(vx, vy, vz));
                    // List<Integer>tem = new ArrayList<>();
                    // tem.add(vx);
                    // tem.add(nums[y]);
                    // tem.add(nums[tz]);
                    // if(!ans.contains(tem))
                    //     ans.add(tem);
//                    x++;
                    // z--;
                    //跳过重复值
                    //原因和外层循环中对num[x]的判断一样。避免重复序列
                    while(y<tz && nums[tz]==nums[tz-1]) tz--;
                    while(y<tz && nums[y]==nums[y+1]) y++;
                    //最后不要忘记还有往内同时收缩
                    y++;
                    tz--;
//                    flag = 1;
                    // System.out.println("next start at "+x+","+z);
                    // break;
                }
                else if(vx+vy+vz > 0){
                    //此时太大,右指针左移
                    //先去除重复
                    while(y<tz && nums[tz]==nums[tz-1]) tz--;
                    //再左移一次
                    tz--;

                }else{
                    //此时太小。左指针右移
                    //先去除重复
                    while(y<tz && nums[y]==nums[y+1]) y++;
                    // 再右移一次
                    y++;
                }
            }
//            if(flag == 0)
            x++;
//            z = lens - 1;

        }

        return ans;
    }
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值