Loading

15. [双指针]三数之和

15. 三数之和

方法一:排序+双指针

不重复的要求使我们不能无脑地使用三重循环枚举所有三元组。举个例子,数组中所有元素都为0,即:

[0, 0, 0, 0, 0, 0, 0, 0, ..., 0, 0]

这种方法的时间复杂度仍然为 \(O(N^3)\),,毕竟我们还是没有跳出三重循环的大框架。然而它是很容易继续优化的,可以发现,如果我们固定了前两重循环枚举到的元素 \(a\)\(b\),那么只有唯一的 \(c\) 满足 \(a+b+c=0\)。当第二重循环往后枚举一个元素 \(b^{’}\)时,由于\(b^{’}>b\),那么满足 \(a+b^{'}+c^{'}=0\)\(c^{'}\) 一定有 \(c^{'}<c\),即\(c^{'}\)在数组中一定出现在 \(c\) 的左侧。也就是说,我们可以从小到大枚举 \(b\),同时从大到小枚举 \(c\),即第二重循环和第三重循环实际上是并列的关系

所以在遍历寻找 \(b,c\) 时,我们可以使用「双指针」法。在枚举的过程每一步中,「左指针」会向右移动一个位置(也就是题目中的 \(b\)),而「右指针」会向左移动若干个位置,这个与数组的元素有关,但我们知道它一共会移动的位置数为\(O(N)\),均摊下来,每次也向左移动一个位置,因此时间复杂度为可以从 \(O(N^2)\)优化为\(O(N)\)

// 执行用时: 25 ms , 在所有 Java 提交中击败了 59.96% 的用户 
// 内存消耗: 43.3 MB , 在所有 Java 提交中击败了 12.15% 的用户

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        int n = nums.length;
        Arrays.sort(nums); //
        List<List<Integer>> res = new ArrayList<List<Integer>>();

        // 枚举 a
        for (int first = 0; first < n; first++){
            // a 需要和上次枚举的数不同
            if (first > 0 && nums[first] == nums[first - 1]){
                continue;
            }
            // c 对应的指针初始化
            // 指向数组的最右端
            int third = n - 1;
            int target = -1 * nums[first];
            // 枚举 b
            for(int second = first + 1; second < n; second++){
                // b 需要和上次枚举的数不同
                if(second > first + 1 && nums[second] == nums[second - 1]){
                    continue;
                }
                // 保证 b 的指针在 c 的指针的左侧
                while (second < third && nums[second] + nums[third] > target){
                    third--;
                }
                // 双指针重合,不再会有满足 a+b+c = 0 的 c 了,可以退出循环
                if(second == third){
                    break;
                }
                if(nums[second] + nums[third] == target){
                    List<Integer> list = new ArrayList<Integer>();
                    list.add(nums[first]);
                    list.add(nums[second]);
                    list.add(nums[third]);
                    res.add(list);
                }
            }
        }
        return res;

    }
}

下面是可稍作修改就应用于 18. 四数之和 的模板。

// 执行耗时:44 ms,击败了12.87% 的Java用户
// 内存消耗:43.3 MB,击败了10.04% 的Java用户

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        int n = nums.length;
        Arrays.sort(nums); //
        List<List<Integer>> res = new ArrayList<List<Integer>>();
        int target = 0;
        // 枚举 a
        for (int first = 0; first < n - 2; first++){
            // a 需要和上次枚举的数不同
            if (first > 0 && nums[first] == nums[first - 1]){
                continue;
            }
            int left = first + 1, right = n - 1;
            while (left < right){
                int sum = nums[first] + nums[left] + nums[right];
                if (sum < target){
                    while (left < right && nums[left] == nums[++left]);
                } else if (sum > target){
                    while (left < right && nums[right] == nums[--right]);
                } else {
                    List<Integer> list = Arrays.stream(new int[] {nums[first], nums[left], nums[right]}).boxed().collect(Collectors.toList());
                    res.add(list);
                    while (left < right && nums[left] == nums[++left]);
                    while (left < right && nums[right] == nums[--right]);
                }
            }
        }
        return res;

    }
}
posted @ 2020-10-24 10:43  上海井盖王  阅读(99)  评论(0)    收藏  举报