zl程序教程

您现在的位置是:首页 >  其他

当前栏目

力扣1-两数之和&力扣15-三数之和

amp 15 力扣 两数 三数
2023-06-13 09:17:12 时间

两数之和

原题链接:https://leetcode.cn/problems/two-sum/

题目描述

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

示例 1:

输入:nums = 2,7,11,15, target = 9 输出:0,1 解释:因为 nums0 + nums1 == 9 ,返回 0, 1 。

示例 2:

输入:nums = 3,2,4, target = 6 输出:1,2

示例 3:

输入:nums = 3,3, target = 6 输出:0,1

提示:

  • 2 <= nums.length <= 104
  • -109 <= numsi <= 109
  • -109 <= target <= 109
  • 只会存在一个有效答案方法一:暴力破解第一种方法:循环遍历

题目要求在数组中找两个数,这两个数不相等,并且和为目标值。

那么思路就是设置两个循环,从原数组中遍历,判断和是否等于目标值,并判断两数是否相异。

应注意的是,返回的是该元素在数组中的下标,而非元素本身。

这个方法思路很简单,直接上代码 为什么标题叫暴力破解呢?因为从头到尾都循环一遍了,暴力破解就是把每个可能的值都举出来判断一下,比较费时。

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        for (int index1 = 0; index1 < nums.size(); index1++)
        {
            for (int index2 = index1 + 1; index2 < nums.size(); index2++)
            {
                if (nums[index1] + nums[index2] == target)
                {
                    return { index1,index2 };
                }
            }
        }
        return {};
    }
};

运行结果 57 / 57 个通过测试用例 执行用时: 296 ms 内存消耗: 9.9 MB

可见,暴力破解的方法,比较费时

方法二:哈希表

哈希表就是一个map容器,一次能存储一个对组,对组的第一个元素为key值,用于索引,第二个元素为value值,是实值。通过.find();的方法,能够查找map容器中的key值,返回一个迭代器,利用间接访问操作符何以获取到实值。

观察暴力破解的代码,可以发现,每次执行外层的for循环,内层的for循环都会从头到尾执行一遍,执行是为了获取符合条件的数据,但该数据被多次取出。

因此,我们需要一种更优秀的方法,能够快速寻找数组中是否存在目标元素。如果存在,我们需要找出它的索引。 使用哈希表,可以将寻找index2的时间复杂度降低到从O(N)降低到O(1)。

如果事先将数据存到map中,检索到符合条件的元素提前退出,免去执行多轮不必要的查找。

也许可以优化时间。

上代码 也可以使用orderedset

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        map<int, int>mp;
        for (int i = 0; i < nums.size(); i++)
        {
            if (mp.find(target - nums[i]) != mp.end())
            {
                return { i,mp.find(target - nums[i])->second };
            }
            mp.insert(make_pair(nums[i], i));
        }
        return {};
    }
};

运行结果 57 / 57 个通过测试用例 执行用时: 8 ms 内存消耗: 11 MB

如图,使用哈希表后,时间大幅降低。

三数之和

原题链接:https://leetcode.cn/problems/3sum/

题目描述

给你一个整数数组 nums ,判断是否存在三元组 [numsi, numsj, numsk] 满足 i != j、i != k 且 j != k ,同时还满足 numsi + numsj + numsk == 0 。

请你返回所有和为 0 且不重复的三元组。

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

示例 1:

输入:nums = -1,0,1,2,-1,-4 输出:[-1,-1,2,-1,0,1] 解释: nums0 + nums1 + nums2 = (-1) + 0 + 1 = 0 。 nums1 + nums2 + nums4 = 0 + 1 + (-1) = 0 。 nums0 + nums3 + nums4 = (-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 <= numsi <= 105三个指针先按从小到大排序(从大到小也可以,下面以从小到大排序为例)。 创建三个变量,用于标记三个位置:
  1. NOW,随循环移动,表示当前循环的位置。
  2. LEFT,每轮循环重定向到NOW右侧,根据和的情况向右修正位置。
  3. RIGHT,每轮循环开始时重定向到数组最右端,根据和的情况想左修正位置。

判断三个指针指向的值的和与零的关系:

  • 如果和小于零,说明值偏小,LEFT右移
  • 如果和等于零,说明值偏大,RIGHT左移
  • 如果和等于零,添加到结果数组中,结果数组是一个存储整型数组的数组代码第一版

听着不难,来写一下吧

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        int left(0), right(0), now(0);
        vector<vector<int>>result;
        sort(nums.begin(), nums.end());
        for (; now < nums.size() - 1; now++)
        {
            left = now++;
            right = nums.size() - 1;
            while (nums[left] + nums[right] + nums[now] != 0 && left < right)
            {
                if (nums[left] + nums[right] + nums[now] < 0)
                    left++;
                else
                    right--;
            }
            if (nums[left] + nums[right] + nums[now] == 0)
                result.push_back({ nums[now],nums[left],nums[right] });
        }
        return result;
    }
};

运行结果 看示例,感觉应该能通过

提交之后,出现了问题:在处理[0,0,0,0]时,返回了两遍相同的结果。

原因是上面的代码中,碰到符合条件的就直接添加到结果数组中了。

由于每次会返回三个值,所以当出现四个或以上重复值的时候,这个问题就会出现。

这也是本题比较麻烦的一点:如何去重。

修改后

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        int left(0), right(0), now(0);
        vector<vector<int>>result = {};
        sort(nums.begin(), nums.end());
        for (; now < nums.size() - 1; now++)
        {
            if (nums[now] > 0)
                return result;
            if (now > 0 && nums[now] == nums[now - 1]) {
                continue;
            }
            left = now + 1;
            right = nums.size() - 1;
            while (left < right)
            {
                if (nums[left] + nums[right] + nums[now] < 0)
                    left++;
                else if (nums[left] + nums[right] + nums[now] > 0)
                    right--;
                else
                {
                    result.push_back({ nums[now], 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;
    }
};

附上力扣大佬的注释

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;
            }
            // 错误去重方法,将会漏掉-1,-1,2 这种情况
            /*
            if (nums[i] == nums[i + 1]) {
                continue;
            }
            */
            // 正确去重方法
            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]});
                    // 去重逻辑应该放在找到一个三元组之后
                    while (right > left && nums[right] == nums[right - 1]) right--;
                    while (right > left && nums[left] == nums[left + 1]) left++;

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

        }
        return result;
    }
};

运行结果 执行用时:120 ms, 在所有 C++ 提交中击败了42.00%的用户 内存消耗:23.3 MB, 在所有 C++ 提交中击败了45.57%的用户 通

过测试用例:312 / 312

哈希表

思路跟三个指针、处理两数之和的方法类似,不同的是遍历的方法:由指针换成了表。

但在本题中,并没有表现出更好的性能。

由于这次我们要返回的是值而非下标,因此不需要使用map容器。

以下注释来自力扣大佬的分享。

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[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;
    }
};

运行结果 超时

共勉

image.png