15. 3Sum**

15. 3Sum**

https://leetcode.com/problems/3sum/description/

题目描述

Given an array nums of n integers, are there elements a, b, c in nums such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.

Note:

The solution set must not contain duplicate triplets.

Example:

Given array nums = [-1, 0, 1, 2, -1, -4],

A solution set is:

[
  [-1, 0, 1],
  [-1, -1, 2]
]

给定含 n 个整数的数组 S, 找出是否存在 3 个整数使得 a + b + c = 0. 把所有不重复的三元组给找出来.

解题思路

如果令 a + b = − c a + b = -c a+b=c, 那么这道题就是 167. *Two Sum II - Input array is sorted 这道题的加强版. 那么我们可以首先给数组排序, 然后遍历整个数组(直到 nums.size() - 2 的位置, 保证数组中至少有 3 个数), 每一次访问的 nums[i] 可以作为三元组的首位元素 (即 c = -nums[i]), 然后针对 nums[i + 1, .... n - 1] 范围内的元素, 进行 2sum 操作, 即使用对撞指针.
然而还应该注意的是, 题目要求得到不重复的三元组, 那么有两个约束需要考虑 (注意此时数组已经排好序了):

  1. 在设置 c = -nums[i] 的时候, 如果 nums[i + 1] == nums[i], 即存在重复元素, 那么应该要略过;
  2. c 确定好之后, 在 nums[i+1, .... n-1] 进行 2sum 处理时, 可能也会存在大量重复元素, 此时应使用 while 循环将这些相同的元素给略过.

C++ 实现 0

20210329 更新: 注意要处理重复元素以及不能忘了先排序.

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        if (nums.size() < 3) return {};
        std::sort(nums.begin(), nums.end());
        vector<vector<int>> res;
        for (int i = 0; i < nums.size() - 2; ++ i) {
            int l = i + 1, r = nums.size() - 1;
            int target = -nums[i];
            while (l < r) {
                int sum = nums[l] + nums[r];
                if (sum == target) {
                    res.push_back({nums[i], nums[l], nums[r]});
                    while (l + 1 < r && nums[l + 1] == nums[l]) ++ l;
                    while (r - 1 > l && nums[r - 1] == nums[r]) -- r;
                    ++ l;
                    -- r;
                } 
                else if (sum > target) -- r;
                else ++ l;
            }
            while (i + 1 < nums.size() - 2 && nums[i + 1] == nums[i]) ++ i;
        }
        return res;
    }
};

C++ 实现 1

时间复杂度 O ( n 2 ) O(n^2) O(n2), 空间复杂度 O ( 1 ) O(1) O(1).

这个实现提供了辅助函数 twoSum, 用于处理 nums[i, ...., j] (闭区间) 内的数据. 先来看 threeSum 的逻辑.

  • threeSum 的逻辑:
  1. 首先要保证 nums 中的元素至少有 3 个, 然后对数组进行排序, 如果此时数组中全是正数或者全是负数, 就不用处理了, 判断方法看最大值和最小值即可.
  2. 依次访问数组中的元素 nums[i]: 令 c = -nums[i], 然后对 nums[i + 1, N - 1] 范围内的数据进行 2Sum, 找到所有满足条件的元素. 为了避免最后结果重复, 如果 nums[i] == nums[i - 1], 那么就需要略过. 最后返回结果 res.
  • twoSum 的逻辑:
  1. nums[i, ..., j] 范围内的元素中查找 ab 满足 a + b = target. 方法与 167. *Two Sum II - Input array is sorted 一样. 然而, 为了避免结果重复, 在查找到 sum == target 时, 使用

    // 注意此时 i 指向当前最后一个重复元素, 比如 [1, 1, 1, 1, 2],
    // 那么这个 while 循环将使得 i 最后指向第 4 个 1. 所以跳出 while 循环
    // 之后, 还要对 i 进行 ++ 操作. 我一开始没有加, 通过调试才发现这个 bug
    while (i + 1 <= j && nums[i + 1] == nums[i]) ++ i;
    while (j - 1 >= i && nums[j - 1] == nums[j]) -- j;
    ++ i; // 这两行代码不能忘了...
    -- j;
    

将重复元素略过.

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        if (nums.size() < 3) return {};
        std::sort(nums.begin(), nums.end());
        if (!nums.empty() && (nums.front() > 0 || nums.back() < 0))
            return {};
        vector<vector<int>> res;
        int i = 0;
        while (i < nums.size() - 2) {
            if (i > 0 && nums[i] == nums[i - 1]) ++ i;
            else {
                int target = -nums[i];
                auto pairs = twoSum(nums, i + 1, nums.size() - 1, target);
                if (!pairs.empty())
                    for (auto &pair : pairs)
                        res.push_back({nums[i], nums[pair[0]], nums[pair[1]]});
                ++ i;
            }
        }
        return res;
    }
private:
    vector<vector<int>> twoSum(vector<int> &nums, int i, int j, int target) {
        // nums[i, ..., j]
        if (j <= i || nums.empty()) return {};
        vector<vector<int>> res;
        while (i < j) {
            int sum = nums[i] + nums[j];
            if (sum == target) {
                res.push_back({i, j});
                while (i + 1 <= j && nums[i + 1] == nums[i]) ++ i;
                while (j - 1 >= i && nums[j - 1] == nums[j]) -- j;
                ++ i;
                -- j;
            }
            else if (sum < target) 
                ++ i;
            else
                -- j;
        }
        return res;
    }
};

C++ 实现 2

1 年 8 个月前的实现. 这里的实现与上面不同之处在于

if (i == 0 || (i > 0 && nums[i] != nums[i - 1]))

这个判断的使用… 写法不同… 觉得有必要记下来 😋

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        if (nums.size() < 3)
            return {};

        vector<vector<int>> res;
        std::sort(nums.begin(), nums.end());
        for (int i = 0; i < nums.size() - 2; ++i) {
          	// 当 i == 0 时, 进行判断; 当 i > 0 时, 需要判断 nums[i]
          	// 是否和前一个元素相等, 如果相等, 就不需要考虑了.
            if (i == 0 || (i > 0 && nums[i] != nums[i - 1])) {
                int lo = i + 1, hi = nums.size() - 1, target = 0 - nums[i];
                while (lo < hi) {
                    vector<int> path;
                  	// 注意, 当在 nums[i+1, .... n-1] 范围内找到了 target, 在考虑
                  	// 下一个 pair 之前, 还要使用两个 while 循环判断 nums[lo] 和 
                  	// nums[lo+1] 是否相等.
                    if (nums[lo] + nums[hi] == target) {
                        path.insert(path.end(), {nums[i], nums[lo], nums[hi]});
                        res.push_back(path);

                        while (lo < hi && nums[lo] == nums[lo + 1]) lo ++;
                        while (lo < hi && nums[hi] == nums[hi - 1]) hi --;
                        lo ++;
                        hi --;
                    }
                    else if (nums[lo] + nums[hi] < target)
                        lo ++;
                    else
                        hi --;
                }
            }
        }
        return res;
    }
};

C++ 实现 3

这个是 1 年 5 个月前提交的结果, 我也曾 beats 100%! 这里的实现与上面不同之处在于对 c 的处理.

在这里插入图片描述

class Solution {
private:
    vector<vector<int>> twoSum(vector<int> &nums, int start, int target) {
        vector<vector<int>> res;
        int i = start, j = nums.size() - 1;
        while (i < j) {
            if (nums[i] + nums[j] == target) {
                res.push_back({nums[i], nums[j]});
                while (i < nums.size() - 1 && nums[i + 1] == nums[i]) i ++;
                while (j > start && nums[j - 1] == nums[j]) j --;
                i ++;
                j --;
            }
            else if (nums[i] + nums[j] < target)
                ++ i;
            else
                -- j;
        }
        return res;
    }
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        if (nums.size() < 3)
            return {};

        vector<vector<int>> res;
        std::sort(nums.begin(), nums.end());
        for (int i = 0; i < nums.size() - 2; ++i) {
            auto temp = twoSum(nums, i + 1, -nums[i]);
            if (!temp.empty())
                for (auto &v : temp)
                    res.push_back({nums[i], v[0], v[1]});
                    
			// 在这里考虑三元组的首元素可能重复的问题.
            while (i + 1 < nums.size() - 2 && nums[i + 1] == nums[i]) ++ i;
        }
        return res;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值