[LeetCode] 15. 三数之和

img

1. 问题描述

15. 三数之和 - 力扣(LeetCode)

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。

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

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

示例 2:

输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。

示例 3:

输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。

提示:

  • 3 <= nums.length <= 3000
  • 105 <= nums[i] <= 105

2. 实现思路

2.1 自己的思路

~~(一定要写出来啊喂)~

此题有两个要注意避免重复的

  • 三个数在数组中的下标
  • 三个数本身

这里对第二点想不太出来怎么去重,第一点的话实现其实是有一点问题的,原本以为三个下标之和不同就能保证三个下标不同了,但显然并不是(例如1,2,3和0,2,4)

以下代码在面对测试用例[0,0,0,0]时报错

public class Solution
{
    public IList<IList<int>> ThreeSum(int[] nums)
    {
        var dict = new Dictionary<int, List<(int, int)>>();
        IList<IList<int>> ans = new List<IList<int>>();
        var h = new HashSet<int>();

        for (int i = 0; i < nums.Length; i++)
        {
            for (int j = i + 1; j < nums.Length; j++)
            {
                int sum = nums[i] + nums[j];
                if (dict.ContainsKey(sum))
                    dict[sum].Add((i, j));
                else
                    dict.Add(sum, [(i, j)]);
            }
        }

        for (int i = 0; i < nums.Length; i++)
        {
            if (dict.TryGetValue(-nums[i], out var l))
            {
                foreach ((var i1, var i2) in l)
                {
                    if (i != i1 && i != i2 && i1 != i2 && !h.Contains(i + i1 + i2))
                    {
                        ans.Add([nums[i], nums[i1], nums[i2]]);
                        h.Add(i + i1 + i2);
                    }
                }
            }
        }

        return ans;
    }
}

2.2 题解思路

第15题. 三数之和 | 双指针 | 哈希法 | 去重

  • 方法一:哈希法

果然去重的小细节和剪枝是最麻烦的

class Solution {
public:
    // 在一个数组中找到3个数形成的三元组,它们的和为0,不能重复使用(三数下标互不相同),且三元组不能重复。
    // b(存储)== 0-(a+c)(检索)
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        
        for (int i = 0; i < nums.size(); i++) {
            // 如果a是正数,a<b<c,不可能形成和为0的三元组
            if (nums[i] > 0)
                break;
            
            // [a, a, ...] 如果本轮a和上轮a相同,那么找到的b,c也是相同的,所以去重a
            if (i > 0 && nums[i] == nums[i - 1])
                continue;
            
            // 这个set的作用是存储b
            unordered_set<int> set;
            
            for (int k = i + 1; k < nums.size(); k++) {
                // 去重b=c时的b和c
                if (k > i + 2 && nums[k] == nums[k - 1] && nums[k - 1] == nums[k - 2])
                    continue;
                
                // a+b+c=0 <=> b=0-(a+c)
                int target = 0 - (nums[i] + nums[k]);
                if (set.find(target) != set.end()) {
                    result.push_back({nums[i], target, nums[k]});   // nums[k]成为c
                    set.erase(target);
                }
                else {
                    set.insert(nums[k]);                            // nums[k]成为b
                }
            }
        }

        return result;
    }
};
  • 时间复杂度: O(n^2)

  • 空间复杂度: O(n),额外的 set 开销

  • 方法二:双指针

先对数组排序(默认是从小到大)

定义三个指针:

i,left(指向i+1),right(指向数组末尾)

i是外层for依次遍历的,接下来重点是left和right如何移动?

如果nums[i] + nums[left] + nums[right] > 0,则说明和大了,将right向左移一位

如果< 0则说明小了,将left向右移一位

如此循环即可

C++:

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        // 找出a + b + c = 0
        // a = nums[i], b = nums[left], c = nums[right]
        for (int i = 0; i < nums.size(); i++) {
            // 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
            if (nums[i] > 0) {
                return result;
            }
            // 错误去重a方法,将会漏掉-1,-1,2 这种情况
            /*
            if (nums[i] == nums[i + 1]) {
                continue;
            }
            */
            // 正确去重a方法
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            int left = i + 1;
            int right = nums.size() - 1;
            while (right > left) {
                // 去重复逻辑如果放在这里,0,0,0 的情况,可能直接导致 right<=left 了,从而漏掉了 0,0,0 这种三元组
                /*
                while (right > left && nums[right] == nums[right - 1]) right--;
                while (right > left && nums[left] == nums[left + 1]) left++;
                */
                if (nums[i] + nums[left] + nums[right] > 0) right--;
                else if (nums[i] + nums[left] + nums[right] < 0) left++;
                else {
                    result.push_back(vector<int>{nums[i], nums[left], nums[right]});
                    // 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
                    while (right > left && nums[right] == nums[right - 1]) right--;
                    while (right > left && nums[left] == nums[left + 1]) left++;

                    // 找到答案时,双指针同时收缩
                    right--;
                    left++;
                }
            }

        }
        return result;
    }
};

C#:

public class Solution
{
    public IList<IList<int>> ThreeSum(int[] nums)
    {
        var result = new List<IList<int>>();

        Array.Sort(nums);

        for (int i = 0; i < nums.Length - 2; i++)
        {
            int n1 = nums[i];

            if (n1 > 0)
                break;

            if (i > 0 && n1 == nums[i - 1])
                continue;

            int left = i + 1;
            int right = nums.Length - 1;

            while (left < right)
            {
                int n2 = nums[left];
                int n3 = nums[right];
                int sum = n1 + n2 + n3;

                if (sum > 0)
                {
                    right--;
                }
                else if (sum < 0)
                {
                    left++;
                }
                else
                {
                    result.Add(new List<int> { n1, n2, n3 });

                    while (left < right && nums[left] == n2)
                    {
                        left++;
                    }

                    while (left < right && nums[right] == n3)
                    {
                        right--;
                    }
                }
            }
        }

        return result;
    }
}
  • 时间复杂度: O(n^2)
  • 空间复杂度: O(1)

需要注意的地方:

去重

  • 为什么对于i不能判断nums[i] == nums[i + 1]?因为这样的话,对于数据{-1,-1,5},会直接跳过(要判断的是不能有重复的三元组,而三元组内的数可以重复)

2.3 优化(或者敲一遍题解?)

/*
 * @lc app=leetcode.cn id=15 lang=csharp
 *
 * [15] 三数之和
 */

// @lc code=start
public class Solution
{
    public IList<IList<int>> ThreeSum(int[] nums)
    {
        var ans = new List<IList<int>>();
        // 原地排序
        Array.Sort(nums);
        // 双指针
        for (int i = 0; i < nums.Length - 2; i++)
        {
            if (nums[i] > 0) break;
            // a 去重
            if (i > 0 && nums[i] == nums[i - 1]) continue;

            int left = i + 1;
            int right = nums.Length - 1;
            while (left < right)
            {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum > 0) right--;
                else if (sum < 0) left++;
                else
                {
                    ans.Add([nums[i], nums[left], nums[right]]);
                    while (left < right && nums[left] == nums[left + 1]) left++;
                    while (left < right && nums[right] == nums[right - 1]) right--;

                    left++;
                    right--;
                }
            }
        }

        return ans;
    }
}
// @lc code=end


3. 补充知识と杂谈,总结

两个重点

  • 剪枝
  • 去重
posted @ 2026-08-09 13:15  绘星tsuki  阅读(5)  评论(0)    收藏  举报