力扣15. 三数之和

题目来源(力扣):

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

题目描述:

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

基本思路:

相对于 力扣1.两数之和 以及 力扣454. 四数相加 II,此题难度陡然上升。
因为 相对于 力扣1.两数之和 ,此题目需要找3个位置;相对于 力扣454. 四数相加 II ,此题目要求在1个数组而非3个数组中找数。
因此,如果采用哈希法,难度较高(代码在最后给出),需要处理重复问题,每次修改i后需要重新建立一张哈希表等

此题最佳的解法为双指针法,先对原数据进行排序,这里使用快排sort即可,
当i固定时,设j=i+1,k=nums.size()-1,
然后根据val=nums[i]+nums[j]+nums[k]的值来调整j、k的位置,
具体而言,val > 0则k--、val<0则j++,val==0则得到一组答案

参考《代码随想录》中写法,如下:

代码实现:

class Solution
{
public:
    vector<vector<int>> threeSum(vector<int> &nums)
    {
        vector<vector<int>> ans;
        sort(nums.begin(), nums.end()); // 对原数据排序
        for (int i = 0; i < nums.size(); i++)
        {
            if (nums[i] > 0)
                break; // 排序后如果nums[i]>0,而之后的数>=nums[i],则不能有有答案组了,直接退出循环
            if (i > 0 && nums[i] == nums[i - 1])
                continue; // 对nums[i]去重,这样的去重方法还会在“回溯算法”的章节中再次看到
            for (int j = i + 1, k = nums.size() - 1; j < k;)
            {
                int val = nums[i] + nums[j] + nums[k];
                if (val > 0)
                    k--;
                else if (val < 0)
                    j++;
                else
                {
                    ans.push_back({nums[i], nums[j], nums[k]});
                    // 找到一组答案后才会去重,防止j>=k
                    while (j < k && nums[k] == nums[k - 1])
                        k--;
                    while (j < k && nums[j] == nums[j + 1])
                        j++;
                    // 找到答案后,j和k同时收缩
                    k--, j++;
                }
            }
        }
        return ans;
    }
};

时间复杂度O(n^2)

哈希法如下

class Solution
{
public:
    vector<vector<int>> threeSum(vector<int> &nums)
    {
        vector<vector<int>> ans;
        sort(nums.begin(), nums.end());
        // a=nums[i] b=nums[j] c=nums[k]
        for (int i = 0; i < nums.size(); i++)
        {
            if (nums[i] > 0)
                break;
            if (i > 0 && nums[i] == nums[i - 1])//对nums[i]去重
                continue;
            unordered_set<int> m; // 每次确定起点i后,重新建立哈希表
            for (int j = i + 1; j < nums.size(); j++)
            {
                if (j > i + 2 && nums[j] == nums[j - 1] && nums[j - 1] == nums[j - 2])//对nums[j]
去重
                    continue;
                int val = -nums[i] - nums[j];
                if (m.find(val) != m.end())
                {
                    ans.push_back({nums[i], nums[j], val});
                    m.erase(val);
                }
                else
                    m.insert(nums[j]);
            }
        }
        return ans;
    }
};

时间复杂度O(n^2),但是有较大的常数
因为每次确定起点i后,需要重新建立哈希表

补充

这里去重的方式可以结合后面在回溯算法中的搜索树来进行理解
具体而言就是力扣40. 组合总和 II

posted @ 2024-10-22 20:50  HB_Computer  阅读(44)  评论(0)    收藏  举报