算法题:有效三角形的个数、三数之和、四数之和(c++)+双指针法

在这里插入图片描述

欢迎来到我的:世界

希望作者的文章对你有所帮助,有不足的地方还请指正,大家一起学习交流 !


前言


内容

第一题:有效三角形的个数

OJ链接


在这里插入图片描述

思路:排序+双指针法
因为是非负整数的数组 nums,需要找出其中可以组成三角形的三元组的数量。
根据三角形的性质:对于三条边 a、b、c,要构成三角形,必须满足任意两边之和大于第三边,即 a + b > ca + c > bb + c > a。当数组是有序的,设三边为 nums[i]nums[j]nums[k](i < j < k),只要满足 nums[i] + nums[j] > nums[k],就可以保证其他两个条件也满足。

整体思路是:先对数组进行排序,然后从大到小遍历数组,对于每个元素 nums[i],使用双指针法在[0, i - 1]区间内寻找满足三角形条件的组合。

class Solution {
public:
    int triangleNumber(vector<int>& nums) {
        // 对数组进行升序排序,方便后续使用双指针法判断三角形条件
        // 排序后,若 nums[i] + nums[j] > nums[k](i < j < k),则可保证构成三角形
        sort(nums.begin(),nums.end());
        
        // 获取数组的最后一个元素的索引
        int n = nums.size() - 1;
        // 用于记录可以构成三角形的三元组的数量,初始化为 0
        int count = 0;

        // 从数组的最后一个元素开始向前遍历,直到索引为 2 的元素
        // 因为构成三角形至少需要三个元素,所以 i 最小为 2
        for(int i = n; i >= 2; i--) {
            // 初始化双指针,begin 指向区间 [0, i - 1] 的起始位置
            int begin = 0;
            // end 指向区间 [0, i - 1] 的末尾位置
            int end = i - 1;

            // 双指针遍历区间 [0, i - 1],只要 begin 小于 end 就继续循环
            while(begin < end) {
                // 判断是否满足三角形条件:nums[begin] + nums[end] > nums[i]
                if(nums[begin] + nums[end] > nums[i]) {
                    // 若满足条件,说明对于当前的 end,从 begin 到 end - 1 的所有元素
                    // 与 nums[end] 和 nums[i] 都可以构成三角形
                    // 所以可以构成的三角形数量为 end - begin,累加到 count 中
                    count += (end - begin);
                    // 将 end 指针向左移动一位,继续寻找其他可能的组合
                    end--;
                } else {
                    // 若不满足条件,说明当前的 begin 对应的元素太小
                    // 将 begin 指针向右移动一位,尝试更大的元素
                    begin++;
                }
            }
        }
        // 遍历完数组后,count 中记录的就是可以构成三角形的三元组的数量,将其返回
        return count;
    }
};

复杂度分析

时间复杂度:排序的时间复杂度为 O(nlongn),双指针遍历的时间复杂度为O(n^2) ,因此总的时间复杂度为 。
空间复杂度:排序的空间复杂度取决于具体的排序算法,一般为 O(longn),因此总的空间复杂度为 O(longn)

第二题:三数之和

OJ题链接


在这里插入图片描述

解题思路:排序+双指针+set去重 即给定一个包含若干整数的数组 nums,找出数组中所有和为 0 且不重复的三元组 [nums[i], nums[j], nums[k]],其中 i != ji != kj != k
题目要求不能出现重复的三元组,
在这里插入图片描述

但是如何解决重复的三元组,可以利用去重算法或者利用set去重
这里的两种思路,利用set去重,如果你深刻理解set容器的基本方法,应该都可以想到这种方法;

整体的解题思路是先对数组进行排序,这样能方便后续使用双指针法来查找符合条件的三元组,同时利用 set 数据结构自动去重的特性,避免结果中出现重复的三元组,最后将 set 中的元素转移到 vector 中返回。

#include <vector>
#include <algorithm>
#include <set>

class Solution {
public:
    // 该函数用于找出数组中所有和为 0 且不重复的三元组
    std::vector<std::vector<int>> threeSum(std::vector<int>& nums) {
        // 定义一个 set 容器 s1,用于存储满足三数之和为 0 的三元组
        // set 会自动对元素进行排序,并且保证元素的唯一性,可避免结果中出现重复的三元组
        std::set<std::vector<int>> s1;
        // 定义一个二维 vector v1,用于存储最终要返回的结果
        std::vector<std::vector<int>> v1;

        // 对输入的数组 nums 进行升序排序
        // 排序后方便后续使用双指针法,也便于发现重复元素
        std::sort(nums.begin(), nums.end());

        // 外层循环,从数组的最后一个元素开始向前遍历,直到索引为 2 的元素
        // 因为要构成一个三元组,至少需要三个元素,所以 i 的下限是 2
        for (int i = nums.size() - 1; i >= 2; i--) {
            // 如果当前元素 nums[i] 小于 0,由于数组已升序排序,其前面元素也都小于 0
            // 三个负数相加不可能等于 0,所以直接跳出循环,减少不必要的计算
            if (nums[i] < 0) break;

            // 初始化双指针 left 和 right
            // left 指向区间 [0, i - 1] 的起始位置
            int left = 0;
            // right 指向区间 [0, i - 1] 的末尾位置
            int right = i - 1;

            // 双指针遍历,在 [0, i - 1] 区间内寻找另外两个数,使三数之和为 0
            while (left < right) {
                // 计算三个数的和
                int sum = nums[left] + nums[right] + nums[i];

                // 如果和为 0,说明找到了一个满足条件的三元组
                if (sum == 0) {
                    // 将该三元组插入到 set 中
                    s1.insert({nums[left], nums[right], nums[i]});
                    // 左指针右移一位
                    left++;
                    // 右指针左移一位
                    right--;
                } 
                // 如果和大于 0,说明当前的和太大
                // 由于数组升序排序,将 right 指针左移,减小和的值
                else if (sum > 0) {
                    right--;
                } 
                // 如果和小于 0,说明当前的和太小
                // 将 left 指针右移,增大和的值
                else {
                    left++;
                }
            }
        }

        // 遍历 set 中的所有元素
        for (auto s : s1) {
            // 将每个三元组依次添加到 vector v1 中
            v1.push_back(s);
        }

        // 返回包含所有满足条件且不重复的三元组的 vector
        return v1;
    }
};

如果使用去重算法:在寻找过程中,通过跳过重复元素来避免结果中出现重复的三元组。就可以不用使用set容器来实现去重了;

#include <vector>
#include <algorithm>

class Solution {
public:
    // 该函数用于找出数组中所有和为 0 且不重复的三元组
    std::vector<std::vector<int>> threeSum(std::vector<int>& nums) {
        // 定义一个二维向量 v1,用于存储最终找到的所有满足条件的三元组
        std::vector<std::vector<int>> v1;
        // 对数组 nums 进行升序排序,方便后续使用双指针法以及跳过重复元素
        std::sort(nums.begin(), nums.end());

        // 外层循环,从数组的最后一个元素开始向前遍历,直到索引为 2 的元素
        // 因为要构成一个三元组,至少需要三个元素,所以 i 的下限是 2
        for (int i = nums.size() - 1; i >= 2; ) {
            // 如果当前元素 nums[i] 小于 0,由于数组已升序排序,其前面元素也都小于 0
            // 三个负数相加不可能等于 0,所以直接跳出循环,减少不必要的计算
            if (nums[i] < 0) {
                break;
            }
            // 初始化双指针,left 指向数组起始位置
            int left = 0;
            // right 指向 nums[i] 前面一个元素的位置
            int right = i - 1;

            // 双指针遍历,在 [0, i - 1] 区间内寻找另外两个数,使三数之和为 0
            while (left < right) {
                // 计算当前三个数的和
                int sum = nums[left] + nums[right] + nums[i];
                // 如果和为 0,说明找到了一个满足条件的三元组
                if (sum == 0) {
                    // 将该三元组添加到结果容器 v1 中
                    v1.push_back({nums[left], nums[right], nums[i]});
                    // 左指针右移一位
                    left++;
                    // 右指针左移一位
                    right--;

                    // 跳过与当前 left 指向元素相同的元素,避免产生重复的三元组
                    while (left < right && nums[left] == nums[left - 1]) {
                        left++;
                    }
                    // 跳过与当前 right 指向元素相同的元素,避免产生重复的三元组
                    while (left < right && nums[right] == nums[right + 1]) {
                        right--;
                    }
                } 
                // 如果和大于 0,说明当前的和太大
                // 由于数组升序排序,将 right 指针左移,减小和的值
                else if (sum > 0) {
                    right--;
                } 
                // 如果和小于 0,说明当前的和太小
                // 将 left 指针右移,增大和的值
                else {
                    left++;
                }
            }
            // 外层循环指针左移一位
            i--;
            // 跳过与当前 nums[i] 相同的元素,避免产生重复的三元组
            while (i >= 2 && nums[i] == nums[i + 1]) {
                i--;
            }
        }
        // 返回存储所有满足条件且不重复的三元组的二维向量
        return v1;
    }
};

第三题:四数之和

oj题链接

在这里插入图片描述

解题思路:排序+双指针+set去重:这题和上一题非常相似,几乎是一模一样的解题思路,就是在外面套了一层循环即可:通过两层循环固定两个数,再使用双指针法在剩余元素中寻找另外两个数,使得四个数的和等于目标值 target。
在这里我使用set 去重法,另一种方法就不做介绍了,有兴趣的小伙伴可以自己试试;

#include <vector>
#include <algorithm>
#include <set>

class Solution {
public:
    // 该函数用于找出数组中所有和为 target 的不重复四元组
    std::vector<std::vector<int>> fourSum(std::vector<int>& nums, int target) {
        // 定义一个 set 容器 ss,用于存储满足四数之和为 target 的四元组
        // set 会自动对元素进行排序,并且保证元素的唯一性,可避免结果中出现重复的四元组
        std::set<std::vector<int>> ss;
        // 定义一个二维 vector vv,用于存储最终要返回的结果
        std::vector<std::vector<int>> vv;

        // 对输入的数组 nums 进行升序排序
        // 排序后方便后续使用双指针法,也便于发现重复元素
        std::sort(nums.begin(), nums.end());

        // 获取数组的长度
        int n = nums.size();

        // 外层循环,固定第一个数 nums[i]
        // 从数组的最后一个元素开始向前遍历,直到索引为 3 的元素
        // 因为要构成一个四元组,至少需要四个元素,所以 i 的下限是 3
        for (int i = n - 1; i >= 3; i--) {
            // 中层循环,固定第二个数 nums[j]
            // 从 nums[i] 的前一个元素开始向前遍历,直到索引为 2 的元素
            // 同样,要构成四元组,此时剩余元素至少要有两个,所以 j 的下限是 2
            for (int j = i - 1; j >= 2; j--) {
                // 初始化双指针,left 指向数组起始位置
                int left = 0;
                // right 指向 nums[j] 前面一个元素的位置
                int right = j - 1;

                // 计算剩余两个数需要满足的和,为了避免整数溢出,使用 long long 类型
                long long two = (long long)target - nums[i] - nums[j];

                // 双指针遍历,在 [0, j - 1] 区间内寻找另外两个数,使四数之和为 target
                while (left < right) {
                    // 计算当前两个数的和
                    int sum = nums[left] + nums[right];
                    // 如果和等于 two,说明找到了一个满足条件的四元组
                    if (sum == two) {
                        // 将该四元组插入到 set 中
                        ss.insert({nums[left], nums[right], nums[j], nums[i]});
                        // 左指针右移一位
                        left++;
                        // 右指针左移一位
                        right--;
                    }
                    // 如果和大于 two,说明当前的和太大
                    // 由于数组升序排序,将 right 指针左移,减小和的值
                    else if (sum > two) {
                        right--;
                    }
                    // 如果和小于 two,说明当前的和太小
                    // 将 left 指针右移,增大和的值
                    else {
                        left++;
                    }
                }
            }
        }

        // 遍历 set 中的所有元素
        for (auto s : ss) {
            // 将每个四元组依次添加到 vector vv 中
            vv.push_back(s);
        }

        // 返回包含所有满足条件且不重复的四元组的 vector
        return vv;
    }
};

总结


到了最后:感谢支持

------------对过程全力以赴,对结果淡然处之

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值