【每日一题】LeetCode - 三数之和

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。

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

示例

示例 1:

输入:nums = [-1,0,1,2,-1,-4]

输出:[[-1,-1,2],[-1,0,1]]

解释:满足条件的三元组为 [-1, 0, 1][-1, -1, 2]

示例 2:

输入:nums = [0,1,1]

输出:[]

解释:唯一可能的三元组和不为 0

示例 3:

输入:nums = [0,0,0]

输出:[[0,0,0]]

解释:唯一可能的三元组和为 0

思路分析

这个问题的核心思路是找到数组中的三个数,其和为 0,同时要避免重复的组合。为了解决这个问题,我们可以通过排序和双指针的结合来有效地实现。

步骤:

  1. 排序数组 :首先将 nums 数组按升序排序,便于后续使用双指针寻找满足条件的三元组。
  2. 遍历数组:固定一个数,然后使用双指针在剩余的部分查找满足条件的两个数。
  3. 避免重复
    • 如果当前固定的数和前一个数相同,可以直接跳过,以避免重复三元组。
    • 双指针过程中,若左右指针指向的数值和前一个数相同,同样跳过。

这个思路使得代码在处理每个三元组时,只计算不重复的组合。

实现代码

cpp 复制代码
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> ans;
        sort(nums.begin(), nums.end());  // 第一步:排序数组
        for(int a = 0; a < nums.size(); a++) {
            if(nums[a] > 0) break;  // 如果当前数大于0,跳出循环,因为后面的数都更大,不可能和为0
            if(a > 0 && nums[a] == nums[a - 1]) continue;  // 避免重复

            // 初始化双指针
            int left = a + 1, right = nums.size() - 1;
            while(left < right) {
                int sum = nums[a] + nums[left] + nums[right];
                if(sum == 0) {
                    ans.push_back({nums[a], nums[left], nums[right]});
                    // 移动指针避免重复
                    while(left < right && nums[left] == nums[left + 1]) left++;
                    while(left < right && nums[right] == nums[right - 1]) right--;
                    left++;
                    right--;
                } else if(sum < 0) {
                    left++;
                } else {
                    right--;
                }
            } 
        }
        return ans;
    }
};

代码讲解

  1. 排序sort(nums.begin(), nums.end()); 这一步可以简化后续查找过程,因为排序后的数组让双指针查找更有效率。
  2. 外层循环 :遍历数组 nums,每次固定一个数 nums[a]。当 nums[a] > 0 时,直接退出循环,因为在排序数组中,后续数值都更大,无法达到 0
  3. 双指针 :对于每个固定数,用双指针在剩余部分查找两数之和为 -nums[a] 的组合。
    • 如果找到和为 0 的组合,将三元组加入结果集中。
    • 移动指针时检查是否有重复数,若有则跳过,以避免重复解。

时空复杂度分析

  • 时间复杂度O(n^2)
    • 外层循环复杂度为 O(n),双指针查找复杂度为 O(n),因此总体复杂度为 O(n^2)
  • 空间复杂度O(log(n))O(n),取决于排序算法的实现方式。返回结果不计入空间复杂度。

比较其他实现方式

一种直接的实现方式是三重循环,但这样会导致 O(n^3) 的时间复杂度,效率低下。而双指针配合排序的实现方法可以有效地减少时间复杂度到 O(n^2),在大数据量情况下具有显著的性能优势。

总结

通过排序和双指针结合,我们可以高效地解决三数之和问题。该方法不仅能避免重复,还能在 O(n^2) 的时间内找到所有符合条件的三元组,是一种简洁且高效的解法。

暴力破解实现

除了使用双指针的方法,我们还可以采用暴力破解的方法来解决这个问题。暴力破解的思路就是通过三重循环枚举所有可能的三元组,检查其和是否为零。虽然这种方法简单易懂,但效率较低。下面是暴力破解的实现代码:

cpp 复制代码
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> ans;
        for (int i = 0; i < nums.size(); i++) {
            for (int j = i + 1; j < nums.size(); j++) {
                for (int k = j + 1; k < nums.size(); k++) {
                    if (nums[i] + nums[j] + nums[k] == 0) {
                        vector<int> triplet = {nums[i], nums[j], nums[k]};
                        // 检查三元组是否已存在于答案中
                        if (find(ans.begin(), ans.end(), triplet) == ans.end()) {
                            ans.push_back(triplet);
                        }
                    }
                }
            }
        }
        return ans;
    }
};

代码讲解

  1. 三重循环:外层循环遍历第一个数,内层循环遍历第二个和第三个数。
  2. 条件判断:检查三个数的和是否为零,如果满足条件则将三元组加入结果集中。
  3. 避免重复 :在加入结果集之前,使用 find 函数检查该三元组是否已经存在。

时空复杂度分析

  • 时间复杂度O(n^3)
    • 三重循环的实现方式使得时间复杂度达到 O(n^3),当 n 较大时,效率非常低下。
  • 空间复杂度O(n),用于存储结果集,最坏情况下会保存所有三元组。

总结比较

暴力破解法虽然简单直观,但对于大规模数据,效率低下。而通过排序与双指针的方法,可以将时间复杂度降低至 O(n^2),适用于大数据场景。因此,在实际应用中,推荐使用双指针的方法解决三数之和问题。

相关推荐
青山木8 小时前
Hot 100 --- 最小栈
java·数据结构·算法·leetcode
疯狂打码的少年8 小时前
【数据结构】栈的应用:表达式求值(后缀表达式)
java·数据结构·笔记·算法
HiDev_8 小时前
【非标自动化】plc执行顺序问题、扫描周期问题
深度学习·算法·机器学习
高洁018 小时前
工信部教考中心证书-人工智能系列本系列
人工智能·python·深度学习·算法
陈王卜9 小时前
Reduce 算子优化笔记
算法
wabs6669 小时前
关于图论【最短路径之Bellman_ford 算法(判断负权回路)|卡码网95.城市间货物运输II的思考】
数据结构·算法·图论·bellman_ford算法·卡码网·判断负权回路
YouonlyliveonceC9 小时前
《大话数据结构》第4章实战:中缀表达式转后缀 + 后缀表达式求值(完整可运行实现)
算法
phoenix@Capricornus9 小时前
从统计决策到贝叶斯估计
人工智能·算法·机器学习
AICDragon9 小时前
1.8%就够了:K3的896个MoE专家,为什么激活率这么低反而是好事?
人工智能·算法
VALENIAN瓦伦尼安教学设备9 小时前
ASHOOTER激光对中仪如何通过颜色确定调整是否合适
数据库·嵌入式硬件·算法