在算法学习的道路上,三数之和算得上是一个必刷的经典题目。它不仅考察了对数组操作的基本功,更是对双指针思想和去重逻辑的一次全面检验。今天,我使用 C++ 来完成这个题目,并记录下整个思考过程。
1. 问题描述
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。答案中不可以包含重复的三元组。
示例 :
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
2. 解题思路
第一眼看到这道题,最容易想到的办法就是三重for循环暴力解决,如下:
cpp
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)
{
//找到一组解
}
}
}
}
但是实际上这并不是一个好办法,时间复杂度 O(n³)不说,还需要额外处理去重问题。
事实上,由于题目要求结果不能包含重复的三元组,我们能够比较容易地想到对原数组进行排序,因为排序之后能够更容易地处理重复的情况。
此外,由于我们需要寻找三个数,对于我们已经排序好的数组来说,一种常用的方法就是固定一个数,用双指针去寻找其余两个数,这道题,我们固定最前面的元素,让双指针去遍历剩下的元素。
我们还要注意一点,就是题目要求的不能含有重复的三元组,对于按元素大小排好序的数组来说,这并不是一件难事。
思路大体就是这样,下一章我们编写代码,还会附上代码详解。
3. 完整代码
我先贴出完整代码,再详细分析:
cpp
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;//存放最终结果
int n = nums.size();//先拿到原数组大小
if(n < 3) return result;//如果原数组中元素小于3我们可以直接返回
sort(nums.begin(),nums.end());//对原数组的元素重新按照从小到大的顺序进行排序得到排序好的数组
for(int i=0; i<n-2; i++)//为什么i<n-2后面会讲
{
if(nums[i] > 0) return result;//因为数组元素是从小到大排列的,如果最前面的数都大于零,那就可以直接结束了
if(i > 0 && nums[i] == nums[i-1]) continue;//固定元素的去重
int l = i + 1;//左指针指向固定元素的下一个
int r = n - 1;//右指针指向数组最后一个元素
while(l < r)//要确保左指针不能超过右指针
{
int sum = nums[i] + nums[l] + nums[r];//三个数求和,判断sum的值
if(sum > 0)//如果大于零,我们把右指针左移,sum就会变小
{
r--;
}
else if(sum < 0)//如果小于零,就把左指针右移,sum变大
{
l++;
}
else//sum刚好等于零
{
result.push_back({nums[i],nums[l],nums[r]});//先把这个三元组存入result
//双指针的去重,这里篇幅可能比较长,我们下面讲解
while(l < r && nums[l] == nums[l+1]) l++;
while(l < r && nums[r] == nums[r-1]) r--;
l++;
r--;
}
}
}
return result;
}
};
下面讲一下代码中的一些细节问题:
- 首先要讲的就是为什么要把
for循环的边界设为n-2。请大家回顾一下前面的解题思路,这个i作为固定元素的下标,l和r始终都在i的后面。试想一下,假如i能指向倒数第二位或者最后一个元素,那么l和r是不是就越界了?这直接就导致了严重的后果。 - 其次要讲的是双指针 的去重 。要知道,
else不仅在while循环中,它还在for循环中,也就是说对于一个固定的i,双指针l和r要多次移动,直到找出全部与这个i指向元素组合后能满足题目要求的三元组。我们已经知道i是固定的,如果l或者r任一个指向的元素和上一位元素相同,那么另一个也会相同,从而出现重复的三元组。 - 最后要讲的是固定元素 的去重 。我们已经提到过了,双指针会找出对于一个固定
i所有满足条件的三元组,因此,再遇到相同的元素时,就可以直接跳过,不将它作为固定元素了。
4. 写在最后
三数之和这道题,我刷了不下五遍。第一遍一头雾水,第二遍懵懵懂懂,第三遍似懂非懂,第四遍豁然开朗,第五遍------我开始享受这个过程。我的大脑中好像有了i,l,r他们三个真实的写照,它们就在那不停的动来动去,我看到了每个指针他们是怎样跳过重复元素的,又是怎样在sum不满足条件时恰到好处的移动,从而最终找到合适元素的。
回想做这道题的整个过程,其实算法学习就是这样,没有捷径可走。每一道题都是一次修行,每一个 bug 都是一次磨练,享受这种从不懂到懂的过程,我想如果有捷径的话,这就是捷径。
今天我把这道题写进博客,不是为了展示我有多厉害,毕竟这题大把人都能写出来,而是记录下自己思考的过程,给自己一个交代。
最后放上一句真理名言:路漫漫其修远兮,吾将上下而求索。