LeetCode题解 哈希表(三):15 三数之和;18 四数之和

本文详细解析了经典的三数之和与四数之和问题的算法实现,重点介绍了如何通过排序和双指针技巧来高效地找到数组中所有满足条件的不重复三元组和四元组,并讨论了算法中的去重和剪枝策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

15 三数之和 Medium

找出一个数组中,所有满足和为0,且不重复的三元组

这道题的难度在于不重复,一个思想是两层遍历,先定位第一个数,然后确定target,再使用两数之和的思想求解之后的数字

这道题的难度在于去重和剪枝,使用哈希数组细节很多

细节之一:如果数组中有重复的数字,应该怎么办?,比如{-1,-1,1,0},理论上答案是{-1,1,0},但是按照我们此前的思想,就会有两个答案,所以要先去

随想录中给出的哈希算法如下:

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[j], c = -(a + b)
    for (int i = 0; i < nums.size(); i++) {
        // 排序之后如果第一个元素已经大于零,那么不可能凑成三元组
        if (nums[i] > 0) {
            break;
        }
        if (i > 0 && nums[i] == nums[i - 1]) { //三元组元素a去重
            continue;
        }
        unordered_set<int> set;
        for (int j = i + 1; j < nums.size(); j++) {
            if (j > i + 2
                && nums[j] == nums[j-1]
                && nums[j-1] == nums[j-2]) { // 三元组元素b去重
                continue;
            }
            int c = 0 - (nums[i] + nums[j]);
            if (set.find(c) != set.end()) {
                result.push_back({nums[i], nums[j], c});
                set.erase(c);// 三元组元素c去重
            } else {
                set.insert(nums[j]);
            }
        }
    }
    return result;
}

三元组中去除元素a的重复性比较好理解;

去除元素b的时候,为什么要求三个index上的数呢?思考这个问题要从“不像考虑a时,前后去重就可以了”的角度思考。思考一下,如果只比较 j 和 j - 1的数字,如果和nums[i]求和时,nums[i] + nums[j] + nums[j - 1]恰好就等于0呢?

所以,必须得考虑从三个相同的元素中,再去除重复的元素。

那为什么在set中找不到c = -(a + b)时,要记录当前的b值呢?

我们这么考虑,假如有这么三个数字a, b1, b2,且满足a + b1 != 0, a + b1 + b2 = 0

当遍历到nums[j] = b1,时,c = -(a + b1),此时set中还没有数字,所以要把b1记录在set中

此后当遍历到nums[j] = b2时,c = -(a + b2),且又因为b1 = -(a + b2),所以此时就找到了三元组

在LeetCode题解中提交时,时间和空间的复杂度都是很高的

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3nCiQpCj-1670332093826)(C:\Users\19041\AppData\Roaming\Typora\typora-user-images\image-20221206205849365.png)]

接下来就可以看看几乎所有人,都是那么写的,双指针法了

首先还是要先排序,然后固定a = nums[i],关键在于如何找b和c

双指针法的思想是,从除了nums[i]的数之外,设置left = i + 1,和right = nums.size() - 1,此时b = nums[left],c = nums[right]

如果a + b + c > 0,那么就说明三数之和大了点,就要令right左移,令三数之和可能减小(可能的原因是因为有重复的数字)

反之,如果a + b + c < 0,那么就说明三数之和小了点,就要令left右移,令三数之和可能增大(可能的原因是因为有重复的数字)

直到left和right相遇为止。

需要注意的是,这道题还是需要去重a,b,c,只是相较于使用哈希算法,好想了那么一些。

时间复杂度O(n^2),代码如下:

vector<vector<int>> threeSum(vector<int>& nums) {
    sort(nums.begin(), nums.end());
    int left, right;
    vector<vector<int>> result = {};

    for(int i = 0; i < nums.size(); i++) {
        if(nums[i] > 0) break;
		// 去重a
        if(i > 0 && nums[i] == nums[i-1]) continue;
        left = i + 1;
        right = nums.size() - 1;
        while(left < right) {
            if(nums[i] + nums[left] + nums[right] < 0)
                left++;
            else if (nums[i] + nums[left] + nums[right] > 0)
                right--;
            else if(nums[i] + nums[left] + nums[right] == 0){
                result.push_back({nums[i], nums[left], nums[right]});
                left++;
                right--;
                //去重b
                while(left<right && nums[left] == nums[left-1]) left++;
                //去重c
                while(left<right && nums[right] == nums[right+1]) right--;
            }
        }
    }

    return result;
}
18 四数之和

和三数之和不同的是,我们已经不需要考虑哈希了

这道题还有一点不同的是,目标值不是零

按照三数之和的思想,再套一层循环就可以了

以下是随想录中的原话:

四数之和的双指针解法是两层for循环nums[k] + nums[i]为确定值,依然是循环内有left和right下标作为双指针,找出nums[k] + nums[i] + nums[left] + nums[right] == target的情况,三数之和的时间复杂度是O(n2),四数之和的时间复杂度是O(n3) 。

那么一样的道理,五数之和、六数之和等等都采用这种解法。

如果要归在算法的类型中的话,目前已经遇到很多使用双指针思想的题目了

以下为随想录中给出的解法

vector<vector<int>> fourSum(vector<int>& nums, int target) {
    vector<vector<int>> result;
    sort(nums.begin(), nums.end());
    for (int k = 0; k < nums.size(); k++) {
        if (nums[k] > target && nums[k] >= 0) {
            break;
        }
        // 对nums[k]去重
        if (k > 0 && nums[k] == nums[k - 1]) {
            continue;
        }
        for (int i = k + 1; i < nums.size(); i++) {
            if (nums[k] + nums[i] > target && nums[k] + nums[i] >= 0) {
                break;
            }
            // 对nums[i]去重
            if (i > k + 1 && nums[i] == nums[i - 1]) {
                continue;
            }
            int left = i + 1;
            int right = nums.size() - 1;
            while (right > left) {
                if ((long) nums[k] + nums[i] + nums[left] + nums[right] > target) {
                    right--;
                } else if ((long) nums[k] + nums[i] + nums[left] + nums[right]  < target) {
                    left++;
                } else {
                    result.push_back(vector<int>{nums[k], nums[i], nums[left], nums[right]});
                    // 对nums[left]和nums[right]去重
                    while (right > left && nums[right] == nums[right - 1]) right--;
                    while (right > left && nums[left] == nums[left + 1]) left++;
                    // 找到答案时,双指针同时收缩
                    right--;
                    left++;
                }
            }
        }
    }
    return result;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值