算法题打卡力扣第15题:三数之和(mid)

题目描述


解法一:暴力解

用三个for循坏枚举出所有等于的三元组然后再排序筛选出不同的三元组

代码

cpp 复制代码
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        int n = nums.size();
        std::set<std::vector<int>> resultSet;
        for(int i=0;i<n;i++){
            for(int j=i+1;j<n;j++){
                for(int k=j+1;k<n;k++){
                    if(nums[i]+nums[j]+nums[k]==0){
                        std::vector<int> triplet = {nums[i],nums[j],nums[k]};
                        std::sort(triplet.begin(),triplet.end());
                        resultSet.insert(triplet);
                    }
                }
            }
        }
        std::vector<std::vector<int>> finalResult(resultSet.begin(),resultSet.end());
        return finalResult;
    }
};

执行结果:

超出时间限制了。

复杂性分析:

时间复杂度分析:O(n^3)

空间复杂度分析:O(n)

解法二:排序+降维+双指针

a + b + c = 0 等价于 b + c = -a 如果我们能固定一个数 a (即nums[i]),那么问题就转化成了:在数组剩下的部分中,寻找两个数,它们的和等于 -a。 这就变成了我们非常熟悉的 "两数之和" (Two Sum) 问题!

具体步骤:

  • 排序:首先,对整个 nums 数组进行升序排序。这一步至关重要,它是双指针法和去重逻辑的基础。时间复杂度 O(n log n)。
  • 主循环 (固定 a):使用一个 for 循环遍历排序后的数组,i 从 0 到 n-3。nums[i] 就是我们固定的那个数 a。
    优化/剪枝:如果 nums[i] > 0,由于数组已经排序,后面的 nums[left] 和 nums[right] 也必然大于 0,三者之和不可能等于 0。所以,一旦 nums[i] > 0,就可以直接 break 循环了。
    去重 a:这是第一个关键的去重逻辑。如果当前的 nums[i] 和上一个 nums[i-1] 相同,那么以 nums[i] 为基础找到的三元组,必然和以 nums[i-1] 为基础的完全一样。为了避免重复,我们需要跳过这种情况。代码实现:if (i > 0 && nums[i] == nums[i-1]) { continue; }
  • 双指针 (寻找 b 和 c):对于每一个 i,我们在它右边的区间 [i+1, n-1] 内使用双指针。
    初始化左指针 left = i + 1。
    初始化右指针 right = n - 1。
    计算目标和 target = -nums[i]。
  • 移动双指针:进入一个 while (left < right) 循环。
    计算当前和 sum = nums[left] + nums[right]。
    计算当前和 sum = nums [left]+ nums [right]。

情况一:sum == target

我们找到了一个解!将 (nums[i], nums[left], nums[right]) 添加到结果列表中。

去重 b 和 c:这是第二个关键的去重逻辑。在我们找到一组解后,需要同时移动 left 和 right 指针。但如果 nums[left] 的下一个元素和它自己相同,或者 nums[right] 的前一个元素和它自己相同,就会产生重复解。所以,我们需要跳过所有与当前 nums[left] 和 nums[right] 相同的元素。

代码实现:

while (left < right && nums[left] == nums[left+1]) left++;

while (left < right && nums[right] == nums[right-1]) right--;

跳过重复项后,left 和 right 还需要再各前进一步,去寻找下一组不同的解:left++; right--;

情况二:sum < target

和太小了。为了让 sum 变大,我们需要一个更大的数。所以,左指针 left 向右移动:left++。

情况三:sum > target

情况三:sum < 目标

和太大了。为了让 sum 变小,我们需要一个更小的数。所以,右指针 right 向左移动:right--。

  • 循环结束后,返回结果列表。
    g老师
    代码实现:
cpp 复制代码
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        int n = nums.size();
        sort(nums.begin(), nums.end());
        vector<vector<int>> ans;
        // 枚举 a
        for (int first = 0; first < n; ++first) {
            // 需要和上一次枚举的数不相同
            if (first > 0 && nums[first] == nums[first - 1]) {
                continue;
            }
            // c 对应的指针初始指向数组的最右端
            int third = n - 1;
            int target = -nums[first];
            // 枚举 b
            for (int second = first + 1; second < n; ++second) {
                // 需要和上一次枚举的数不相同
                if (second > first + 1 && nums[second] == nums[second - 1]) {
                    continue;
                }
                // 需要保证 b 的指针在 c 的指针的左侧
                while (second < third && nums[second] + nums[third] > target) {
                    --third;
                }
                // 如果指针重合,随着 b 后续的增加
                // 就不会有满足 a+b+c=0 并且 b<c 的 c 了,可以退出循环
                if (second == third) {
                    break;
                }
                if (nums[second] + nums[third] == target) {
                    ans.push_back({nums[first], nums[second], nums[third]});
                }
            }
        }
        return ans;
    }
};

复杂度分析

时间O(n^2)

空间O(n)

相关推荐
元亓亓亓44 分钟前
LeetCode热题100--98. 验证二叉搜索树--中等
算法·leetcode·职场和发展
程序员小富1 小时前
令牌桶VS漏桶:谁才是流量控制的“最优解”?
java·后端·算法
fsnine1 小时前
机器学习回顾(二)——KNN算法
人工智能·算法·机器学习
aiwery1 小时前
算法题——找到字符串中所有字母异位词
javascript·算法
拒绝摆烂1 小时前
LeetCode Hot 100 第7天
算法·leetcode·哈希算法
纵有疾風起2 小时前
数据结构——队列
c语言·数据结构·算法·链表
代码不停2 小时前
双指针算法题目练习
java·算法
youzjuer3 小时前
算法之排序
算法
zhangfeng11333 小时前
Shi-Tomasi 算法和 Harris 角点检测算法都是经典的角点检测方法,但它们在理论基础和实现细节上有一些区别。下面我将详细对比这两种算法。
opencv·算法·计算机视觉
拼好饭和她皆失3 小时前
算法加训 动态规划熟悉30题 ---下
算法·动态规划