leetcode题解15-三数之和(双指针经典)

问题描述

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

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

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]

示例 2:

输入:nums = []
输出:[]

示例 3:

输入:nums = [0]
输出:[]

提示:

0 <= nums.length <= 3000
-105 <= nums[i] <= 105

解题思路(排序+双指针)

如果使用三重循环来枚举每种情况,则时间复杂度为O(N3)。
另外题中还要求答案中不可以包含重复的三元组。虽然可以使用哈希表来进行去重操作,但是这样又大大消耗了空间,而且操作起来相当的不方便,我们可以想到先将数组排好序,这样相同的元素就可以保持相邻。另外,在我们枚举的三元组 (a, b, c)满足 a≤b≤c,保证了只有 (a,b,c) 这个顺序会被枚举到,而(b,a,c)、(c,b,a) 等等这些不会,这样就减少了重复。要实现这一点,我们可以将数组中的元素从小到大进行排序,随后使用普通的三重循环就可以满足上面的要求。

Arrays.sort(nums);
for(int i=0;i<len;i++){
    
    
	for(int j=i+1;j<len;j++){
    
    
		for(int k=j+1;k<len;k++){
    
    
			if(nums[i]+nums[j]+nums[k]==0){
    
    
				//add结果集
				...
			}
		}
	}
}

对于每一重循环而言,相邻两次枚举的元素不能相同,否则也会造成重复。
而对于三重循环,我们可以固定一个指针a,然后从a的后面两端来枚举b和c,即从小到大枚举b,从大到小枚举c。这样就可以保持第二重循环不变,第三重循环变成一个从数组最右端开始向左移动的指针
当我们需要枚举数组中的两个元素时,如果我们发现随着第一个元素的递增,第二个元素是递减的,那么就可以使用双指针的方法,将枚举的时间复杂度从 O(N^2) 减少至 O(N)。为什么是 O(N)呢?这是因为在枚举的过程每一步中,「左指针」会向右移动一个位置(也就是题目中的 bb),而「右指针」会向左移动若干个位置,这个与数组的元素有关,但我们知道它一共会移动的位置数为 O(N),均摊下来,每次也向左移动一个位置,因此时间复杂度为 O(N)。

实现代码:

class Solution {
    
    
    private List<List<Integer>> lists;
	public List<List<Integer>> threeSum(int[] nums) {
    
    
        lists=new ArrayList<List<Integer>>();
        //如果数组长度小于3,无法满足有三个数,直接返回空集
        if(nums.length<3){
    
    
            return lists;
        } 
        Arrays.sort(nums);
        int len=nums.length;
        for(int i=0;i<len-2;i++){
    
    
            //如果最左边的数都大于0,那么后面的数肯定无法满足3数之和为0,直接退出循环
            if(nums[i]>0){
    
    
                break;
            }
            //避免重复
            if(i!=0 && nums[i]==nums[i-1]){
    
    
                continue;
            }
            //定义双指针
            int j=i+1;
            int k=len-1;
            int prej=-1;        //prej为了去除j指针所指的元素的重复情况
            while(j<k){
    
             //当j==k时退出循环
                //如果三数之和大于0,那么k--
                if((nums[j]+nums[k]+nums[i])>0){
    
    
                    k--;
                //如果三数之和小于0,那么j++
                }else if((nums[j]+nums[k]+nums[i])<0){
    
    
                    j++;
                //如果三数之和等于0
                }else if((nums[j]+nums[k]+nums[i])==0){
    
    
                    //当该数在之前已经被枚举到了,仅移动指针
                    if(prej!=-1 && nums[j]==nums[prej]){
    
    
                        k--;
                        j++;
                        continue;
                    }else{
    
    
                    //如果没有,就添加到集合中,并标记这一次成功枚举时的指针j的位置
                        List<Integer> list=new ArrayList<Integer>();
                        list.add(nums[i]);
                        list.add(nums[j]);
                        list.add(nums[k]);
                        lists.add(list);
                        prej=j;
                    }
                    k--;
                    j++;
                }
            }
        }
		return lists;
    }
}

猜你喜欢

转载自blog.csdn.net/qq_39736597/article/details/113809297