LeetCode 15 - 数组 中的3个数之和等于0

这是个关于数组的算法题,属于中等难度,题目是这样的

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 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] 。
注意,输出的顺序和三元组的顺序并不重要。

这个题目应该怎么解呢? => 这个题目的解法,主要是通过 排序 和 双指针来解决,我们先把数组从小到大排序,然后数组元素位置K从0到n-1进行循环 for(int i = 0; i < n; i++)
在每一轮循环中,又有L,R左右2个指针,L从i+1的位置从左往右移动,R从数组末尾位置开始从右往左移动. 每一次移动都计算i,L,R这3个位置的和,是不是等于0. => 这里可以看出,每一轮的循环中,i的位置在这一轮是固定的,比如
第一轮循环i=0,第二轮循环i=1,对应的数组元素值nums[i]在每一轮循环中也是固定的,所以这个题目我们可以把它变为求2数之和,就是求L和R位置的和等于 -nums[i]

我们以数组 nums = [-1,0,1,2,-1,-4] 为例子来讲解整个过程
1. 对该数组从小到大排序

image

2.  开始第1轮循环, i =0,   L 左指针从 i + 1 = 0 + 1 = 1的位置从左向右移动, R右指针从数组末尾 n - 1 = 5的位置从右向左移动、

image

在这一轮循环中,i的位置是不动的,始终在数组下标0这个位置,它的值是 nums[i] = nums[0] = -4   => 所以我们其实就是移动左指针L和右指针R,使它们的和等于4   => 这里就有3个注意点

注意点1

其实这里本质上要做的就是遍历数组中位置i以外的元素,看看它们的和会等于4么?  因为这是个排序好的数组,从左到右,数是越来越大, 所以=>

如果 nums[L] + nums[R]  < 4   =>  那么,L指针要往右边移动,因为这样,新的nums[L]才会更大, (nums[L] + nums[R])的值才能变大,才有可能等于4

如果 nums[L] + nums[R] > 4 =>  那么,R指针要往左边移动,因为这样,新的nums[R]才会更小,(nums[L] + nums[R])的值才能变小,才有可能等于4

注意点2

如果发现 nums[L] + nums[R]  = 4时,那么这是一个结果集 [i,L,R]  => 接下来,L继续往右边移动,R往左边移动,也就是说接下来左右两个指针要同时移动,这点很好理解,因为当前nums[L] + nums[R] =4, L往右边移动nums[L]变大, 要让新的nums[L] + nums[R]  = 4, nums[R]就要变小,所以右指针R就要往左边移动  =>  那么这里就还有可能出现一个点:

nums[L] + nums[R]  = 4  之后,举个例子比如左指针此时L=1, nums[L] = -1,  左指针接下来往右边移动变成L=1+1=2, nums[L] = nums[2] = -1, 和移动前L=1时值一样(nums[1] = -1) => 这个时候,这个移动是没有价值的,因为题干中明确要求不重复的三元组(数组中元素的值不重复,不是数组位置不重复)。所以,我们应该越过本次移动,继续L往右边移动,移动到下一个位置

右指针R也是同样的道理,如果R往左移动一位后,nums[R-1] 和原来的nums[R] 值一样,也要跳过这步移动,右指针继续往左边移动

注意点3

 在左右指针移动过程中,当左指针往右边移动,右指针往左边移动,当左右两个指针相遇时,或者左指针L到右指针R的右边去了。就代表这一轮循环结束了

接下来i往右边移动,移动到i+1的位置,开始下一轮循环 => 但是这里也有一点要注意,如果发现i移动到下一个位置的元素值和当前值相等,也就是说 nums[i] = nums[i + 1], 那么这个位置也要跳过,和上面跳过L,R是一样的道理,这个i的移动没有价值,因为题干中要求不重复的三元组,所以这种情况下,i继续往右边移动,也避免了一次不必要的循环

 

第1轮循环 (i = 0)

image

 image

image

image

左指针L和右指针相遇后,第1轮循环结束。开始第二轮循环,i往右边移动 i = 1

image

image

image

image

image

 

好了,现在我们来看代码  C++版本

 

#include <vector>
using std::vector;

class solution
{
    public:
         vector<vector<int>> GetThreeElements(vector<int>& nums)
         {
            vector<vector<int>> threeEles;
            for(int i = 0; i < nums.size(); i++)
            {
                if((i+1) < nums.size() && nums[i+1] == nums[i])
                {
                    continue;
                }
                vector<vector<int>> twoSumResult = TwoSum(nums, i + 1, nums.size() - 1, -nums[i]);
                for(const auto& twoSumItem in twoSumResult)
{
threeEles.push_back(twoSumItem);
}

//也可以不用上面的for循环,直接用下面的insert方法 // threeEles.insert(threeEles.end(),twoSumResult.begin(), twoSumResult.end()); }
return threeEles; } private: vector<vector<int>> TwoSum(vector<int>& nums,int left, int right, int target) { vector<vector<int>> result; while(left < right) { auto sum = nums[left] + nums[right]; if(sum == target) { vector<int> itemResult; itemResult.push_back(-target); itemResult.push_back(nums[left]); itemResult.push_back(nums[right]); result.push_back(itemResult); if(nums[left + 1] == nums[left]) { left++; } left++; if(nums[right - 1] == nums[right]) { right--; } right--; } else if(sum < target) { left++; } else { right--; } } return result; } };

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted on 2026-10-06 17:11  新西兰程序员  阅读(7)  评论(0)    收藏  举报