【水滴计划 | 每日两题】:三数之和、四数之和

167 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第27天,点击查看活动详情

1、写在前面

大家好,我是翼同学,这里是【水滴计划 | 刷题日志】

每日两题,拒绝摆烂。

2、内容

2.1、题目一:三数之和

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

(1) 描述

image.png

(2) 举例

image.png

image.png

image.png

image.png

(3) 解题

哈希法求解

 // 设 a = nums[i], b = nums[j], 本题需寻找是否有一个元素 c 使得 a+b+c=0,此时 c = -(a+b)
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        // 定义一个二维vector容器用于存放结果
        vector<vector<int> > rs;
        // 对数组nums进行排序
        sort(nums.begin(), nums.end());
        // 遍历数组 nums 取到 a
        for (int i = 0; i < nums.size(); i++) {
            // 由于数组已经排好序,此时如果数组首元素已经大于零,则不存在三元组使得相加等于零
            if (nums[i] > 0) {
                break;
            }
            // 去重 a 操作
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            // 定义一个unordered_set容器record 用于寻找容器里是否有 -(a+b)
            unordered_set<int> record;
            // 再次遍历数组,取到 b
            for (int j = i + 1; j < nums.size(); j++) {
                // 去重 b 操作
                if (j>i+2 && nums[j] == nums[j-1] && nums[j-1] == nums[j-2]) {
                    continue;
                }
                // 取值 c
                int c = 0 - (nums[i] + nums[j]);
                // 如果在容器record中寻找到 c,则插入该三元组 (a, b, c) 到容器 rs 中
                if (record.find(c) != record.end()) {
                    // 插入一个结果
                    rs.push_back({nums[i], nums[j], c});
                    // 去重 c 操作
                    record.erase(c);// 三元组元素c去重
                }
                // 如果在容器record中找不到的 c,则记录这个 b
                else {
                    record.insert(nums[j]);
                }
            }
        }
        // 最后返回结果
        return rs;
    }
};

难点在于不能包含重复的三元组,因此需要去重操作。

双指针求解

在双指针解法中,利用一个for循环遍历一遍数组,循环变量i从下标0开始。此时再定义两个变量leftright,分别指向i+1nums.size()-1。设a = nums[i]b = nums[left]c = nums[right],则题目要求就是寻找到a + b + c = 0,由于数组已经排好序,如果当前a + b + c < 0,则说明三数之和偏小,此时应该将left向后移动。这样三数之和才有变大的可能。如果当前a + b + c > 0,则说明三数之和偏大,此时应该将right向前移动。这样三数之和才有变小的可能。最后如果leftright相遇,则退出当前循环。

难点依旧是去重操作。需要注意的是,我们去重后的结果是没有重复的三元组,但三元组内的元素是可以重复的!

参考代码如下:

// 设定 a = nums[i], b = nums[left], c = nums[right],此时题目要求为找出 a + b + c = 0
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        // 定义一个二维容器用于存放运算结果
        vector<vector<int> > rs;
        // 将数组nums排好序
        sort(nums.begin(), nums.end());
        // 循环遍历 nums 数组
        for (int i = 0; i < nums.size(); i++) {
            // 由于数组已经排好序,如果元素i已经大于或等于零,则nums[i]和后续元素是不可能凑成三元组,因此返回运算结果即可
            if (nums[i] > 0) return rs;
            // 去重操作一,将 a 去重
            if (i > 0 && nums[i] == nums[i - 1]) continue;
            // 定义两个指针(其实就是表示数组下标)
            int left = i + 1;
            int right = nums.size() - 1;
            // 当两个指针相遇则退出循环
            while (right > left) {
                // 当 a + b + c > 0 则应将 right 向左边移动,三数之和才有变小的可能
                if (nums[i] + nums[left] + nums[right] > 0) {
                    right--;
                }
                // 当 a + b + c < 0 则应将 left 向右边移动,三数之和才有变大的可能
                else if (nums[i] + nums[left] + nums[right] < 0) {
                    left++;
                }
                // 当 a + b + c = 0 则表示已找到三数之和,此时将结果插入到rs中并进行去重操作
                else {
                    // 将三元组 (a, b, c) 插入到运算结果中
                    rs.push_back(vector<int>{nums[i], nums[left], nums[right]});
                    // 去重操作二,将 b 去重
                    while (right > left && nums[right] == nums[right - 1]) {
                        right--;
                    } 
                    // 去重操作二,将 c 去重
                    while (right > left && nums[left] == nums[left + 1]) {
                        left++;
                    }
                    // 找到答案时,双指针同时收缩
                    right--;
                    left++;
                }
            }
        }
        // 最后返回运算结果
        return rs;
    }
};

image.png

2.2、题目二:四数之和

链接:18. 四数之和 - 力扣(LeetCode)

(1) 描述

image.png

(2) 举例

image.png

image.png

image.png

(3) 解题

解题代码如下:

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        // 定义一个二维数组 rs 用于存放运算结果
        vector<vector<int> > rs;
        // 将数组排好序
        sort(nums.begin(), nums.end());
        // 第一层for遍历数组 nums
        for (int i = 0; i < nums.size(); i++) {
            // 遇到特殊情况,可直接退出循环
            if (nums[i] > target && nums[i] >= 0) {
            	break;
            }
            // 去重操作,对 nums[k] 去重
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            // 第二层for循环遍历
            for (int j = i + 1; j < nums.size(); j++) {
                // 遇到特殊情况,则退出循环
                if (nums[i] + nums[j] > target && nums[i] + nums[j] >= 0) {
                    break;
                }
                // 去重操作,对 nums[i] 去重
                if (j > i + 1 && nums[j] == nums[j - 1]) {
                    continue;
                }
                // 定义两个指针,分别指向下一个元素和数组末尾元素
                int left = j + 1;
                int right = nums.size() - 1;
                // 当 left > right 则退出循环
                while (left < right) {
                    // 如果四数之和偏大,则让right向左移动
                    if ((long) nums[i] + nums[j] + nums[left] + nums[right] > target) {
                        right--;
                    } 
                    // 如果四数之和偏小,则让left向右移动
                    else if ((long) nums[i] + nums[j] + nums[left] + nums[right]  < target) {
                        left++;
                    }
                    // 最后找到了四数之和
                    else {
                        // 将四元组存入运算结果中
                        rs.push_back(vector<int>{nums[i], nums[j], 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 rs;
    }
};

3、写在最后

好了,今天就刷到这里,明天再来。