Leetcode 三数之和

解题思路:

  1. 排序数组:首先对数组进行排序,以便使用双指针技术来查找三元组。
  2. 双指针法:在遍历数组时,遍历固定三元组的第一个元素,然后使用双指针(分别指向剩下数组的头和尾并相向而行,因为下标两两不同)来寻找另外两个元素,使三者之和为零。
  3. 去重处理:为了避免重复的三元组,跳过重复的元素。

为什么需要对原始数组进行排序?

在这道题中,对数组进行排序是为了简化解决过程,并有效避免重复结果。排序在解决这类问题时的作用是不可忽视的。下面详细解释为什么排序是必要的,以及不排序会遇到什么样的问题。

1. 简化双指针操作

排序的一个主要目的是方便使用双指针 技巧。在经过排序的数组中,我们可以利用双指针分别从数组的两端开始,来寻找和为 0 的三元组。 排序后,双指针可以轻松地通过调整指针(根据当前和的大小)来决定向哪边移动,从而减少时间复杂度

如果不进行排序,双指针策略将无法应用,因为在无序的数组中,无法简单判断是否应该移动左指针还是右指针来缩小差距 。我们会陷入遍历每一个组合的情况,这样会使时间复杂度增加至 O ( n 3 ) O(n^3) O(n3),性能远远不如 O ( n 2 ) O(n^2) O(n2) 的双指针方法。

2. 去重处理

排序的另一个重要作用是去除重复的三元组。排序后,相同的数字会排在一起,因此在遍历时很容易跳过重复的元素。这个去重过程是通过在遍历中跳过连续相同的元素来实现的。

如果数组没有排序,想要去重就需要额外的数据结构(如哈希表)来存储已经出现的三元组,并且需要进行额外的查找操作,这样就会增加时间和空间的复杂度。

3. 使用排序的例子

假设我们有一个数组 [-1, 0, 1, 2, -1, -4],如果不排序,我们会发现所有可能的三元组(假设通过暴力搜索),会包含:

复制代码
[-1, 0, 1]
[0, -1, 1]
[-1, 2, -1]
[-4, 1, 2]

可以看到,有些三元组(如 [-1, 0, 1][0, -1, 1])在不排序的情况下会被计算多次。而排序后,我们可以确保相同的数字只出现一次,并且可以跳过重复的三元组,得到的结果更加简洁。

4. 避免 O ( n 3 ) O(n^3) O(n3) 的暴力解法

不排序的情况下,你可以通过三重循环遍历所有的三元组来解决问题,即暴力解法,其时间复杂度是 O ( n 3 ) O(n^3) O(n3)。在实际场景中,如果输入数组较大,这种方法的效率会非常低。通过排序并使用双指针方法,我们可以将时间复杂度优化到 O ( n 2 ) O(n^2) O(n2)。

总结:

虽然不排序也可以通过某些方法找到和为 0 的三元组,但排序有如下显著优势:

  1. 使用双指针减少时间复杂度到 O ( n 2 ) O(n^2) O(n2)。
  2. 简化了去重逻辑,避免了复杂的查重操作。
  3. 排序后方便高效地跳过重复元素。

因此,排序在这道题中是必要的,它使得解决方案更加高效和简单。

注意点:

不仅需要在固定三元组第一个元素时进行脱重处理,同时也需要在双指针移动时,匹配到三元组时对双指针指向的元素进行脱重处理!!

固定三元组第一个元素进行脱重处理时,从第二个元素开始进行脱重判断

cpp 复制代码
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result; //创建一个存储结果的向量
        sort(nums.begin(), nums.end()); //先对原始数组进行排序

        for(int i = 0; i < nums.size(); i++) {
            //然后首先进行去重处理,从第二个元素开始进行判断
            if(i > 0 && nums[i] == nums[i-1]) continue;
            
            //初始化双指针
            int left = i + 1;
            int right = nums.size() - 1;
            
            //初始完双指针之后,两个指针开始移动,并且移动需要有终止条件,那就是左指针小于右指针
            while(left < right) {
                int sum = nums[left] + nums[right] + nums[i];
                if(sum == 0) {
                    result.push_back({nums[i], 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 result;
    }
};

时间复杂度:

  • 排序的时间复杂度是 O ( n log ⁡ n ) O(n \log n) O(nlogn)。
  • 遍历数组和使用双指针查找的时间复杂度是 O ( n 2 ) O(n^2) O(n2)。
  • 因此总的时间复杂度为 O ( n 2 ) O(n^2) O(n2)。

空间复杂度:

  • 排序算法使用 O ( log ⁡ n ) O(\log n) O(logn) 的空间,此外只有常数级别的额外空间,因此空间复杂度为 O ( n ) O(n) O(n)(不考虑输出结果的空间)。
相关推荐
PownShanYu20 分钟前
RainbowDash 的 Robot
算法
Phoebe鑫38 分钟前
数据结构每日一题day11(链表)★★★★★
数据结构·算法
独好紫罗兰1 小时前
洛谷题单3-P2669 [NOIP 2015 普及组] 金币-python-流程图重构
开发语言·python·算法
跳跳糖炒酸奶1 小时前
第四章、Isaacsim在GUI中构建机器人(3):添加摄像头和传感器
人工智能·python·算法·ubuntu·机器人
Jay_See1 小时前
Leetcode——239. 滑动窗口最大值
java·数据结构·算法·leetcode
凯强同学1 小时前
第十四届蓝桥杯大赛软件赛省赛Python 研究生组:4.互质数的个数
python·职场和发展·蓝桥杯
肠胃炎1 小时前
真题246—矩阵计数
java·线性代数·算法·矩阵·深度优先
什码情况1 小时前
微服务集成测试 -华为OD机试真题(A卷、JavaScript)
javascript·数据结构·算法·华为od·机试
罗西的思考3 小时前
[2W字长文] 探秘Transformer系列之(23)--- 长度外推
人工智能·算法
算AI1 天前
人工智能+牙科:临床应用中的几个问题
人工智能·算法