[LeetCode-中等-排序和双指针] 找出数组中三数之和满足特定条件的三个数

之前,我做了一道简单的LettCode题,二数之和  https://www.cnblogs.com/wphl-27/p/17861539.html,  用的是数组和Map来实现的

现在这个题目是三数之和,题目是这样的

给定一个整数数组 intArr, 判断是否存在三元组 [intArr[i],intArr[j],intArr[k]]  满足条件 i != j, i != k, j != k, 同时满足 intArr[i] + intArr[j] + intArr[k] = 0.   要求返回所有和为0并且不重复的三元组

这里,有一点注意,就是答案不能包含重复的三元组 =》  这是个什么概念呢 , 比如  

intArr = [-1,0,1,3,-1,-4]  这里 intArr[0]  + intArr[1]  + intArr[2] =  -1 + 0 + 1 = 0, 所以三元组 [-1,0,1]  但是同时 intArr[1] + intArr[2] + intArr[4] = 0 + 1 + (-1) = 0 这里三元组 是 [0,1,-1]

特别注意:  这里 [-1,0,1]  和 [0,1,-1] 两个三元组里面元素值相同,只是在数组中位置不同,我们认为是重复的三元组,所以只能算一个  =》 这点要特别注意

 

拿到这个题目,你首先想到的是什么,肯定是最暴力的解法,就是三层循环,对吧

第1层循环:  i 从第1个元素到数组最后一个元素

第2层循环:  j 从第(i+1)个元素到数组最后一个元素

第3层循环:  k 从第(j+1)个元素到数组最后一个元素

只要 intArr[i] + intArr[j] + intArr[k] = 0, 就是满足条件的。 但是这种情况,还是会存在上面说的我们重复的三元组,比如 [-1,0,1]  和 [0,1,-1]这种情况

那么,如何来避免出现这种重复的三元组呢,你能想到办法么?

办法就是: 把原始数组从小到大排序,比如我们上面的例子,排完序后为 [-1,-1,0,1,3,-4],排完序能保证什么呢 =>【如果数组中存在相同的元素排完序能够保证相同的2个元素一定是相邻的 】。 那么我们要保证最后的结果中不能出现以下情况:

[-1,-1,0,1,3,-4]  =》 i = 0, j = 2, k = 3   结果 (-1,0,1)   第1层循环枚举到第一个元素 -1

[-1,-1,0,1,3,-4]  =》 i = 1, j = 2, k = 3   结果 (-1,0,1)   第1层循环枚举到第二个元素 -1 => 和上次枚举到的第一个元素相同,我们需要跳过这一步,直到寻找到下一个不同的元素0

上面这两次结果完全相同,如何避免出现这种情况呢, 办法就是: 针对排完序之后的数组[-1,-1,0,1,3,-4], 每一层循环中,如果发现这次枚举到的元素和上一次枚举到的元素相同,那就果断放弃这次枚举,跳到下一轮循环

我们再来看一个例子理解:  假如排完序之后的数组为 [0,1,1,2,2,2,3]   第一层循环走到第2个元素时,发现它和第1个元素相同,都是1。 显然,这个时候,你在第一层循环走到第2个元素1时,后面第2层第三层循环走过的路,比如(1,2,2)等,在你上次第一层循环走到第1个元素1时,都已经走过了,所以造成重复。 所以,在一个有序数组中,我们在同一层循环中,一旦发现这次枚举到的元素和上一次枚举到的元素值相同,就果断skip掉它

基于这个分析,我们来写出最暴力的解法C++

class Solution {
public:
    vector<vector<int>> GetThreeSum(vector<int>& arrs)
    {
        int arrSize = arrs.size();
        if (arrSize < 3) return {};
        sort(arrs.begin(), arrs.end()); //对数组排序
        vector<vector<int>> rtnArr; //返回的数组

        for (int i = 0; i < arrSize; i++) //第1层循环
        {
            if (arrs[i] > 0) return rtnArr; // 数组是从小到大排序的,如果第一个数就大于0,后面的都是递增的正数,不可能最后结果相加为0
            if (i > 0 && arrs[i] == arrs[i - 1])
                continue;

            for (int j = i + 1; j < arrSize; j++) //第2层循环
            {
                if (j > i + 1 && arrs[j] == arrs[j - 1])
                    continue;

                for (int k = j + 1; k < arrSize; k++) //第3层循环
                {
                    if (k > j + 1 && arrs[k] == arrs[k - 1])
                        continue;

                    if (arrs[i] + arrs[j] + arrs[k] == 0)
                        rtnArr.push_back(arrs[i], arrs[j], arrs[k]);
                }
            }

        }

        return rtrArr;

    }

};

 

这种方法确实可以解决这个问题,但3个循环,时间复杂度为O(N*N*N). 有没有什么方法来优化呢  => 我们需要换一个思路,在第1层循环之后,比如

比如针对数组 [-1,-1,0,1,3,-4]
for (int i = 0; i < arrSize; i++) //第1层循环 {
我们需要在数组中位置在i之后的元素中,找到两个元素,这两个元素的和等于 (0-arrSize[i]). 而数组中位置为i之后的元素是有序的 =》 问题就变成了在一个有序数组中,找到和为某个值的两个数 => 会不会想到用双指针来实现,左指针从i+1的位置从左到右移动,右指针从数组最后一个位置
往左移动,直到两个指针相遇,找出这个移动过程中和等于
(0-arrSize[i])的两个元素就可以
 }
C++ 代码修改如下:
public:
    vector<vector<int>> threeSum(vector<int>& arrs)
    {
        int arrSize = nums.size();
        if (arrSize < 3)   return {};          
        vector<vector<int>> rtnArr;            // 返回的数组
        std::sort(nums.begin(), nums.end());// 对数组排序(默认递增)
        for (int i = 0; i < arrSize; i++)      // 第1轮循环
        {
            if (arrs[i] > 0)    return rtnArr; //  数组是从小到大排序的,如果第一个数就大于0,后面的都是递增的正数,不可能最后结果相加为0
            // 去重:如果此数已经选取过,跳过
            if (i > 0 && arrs[i] == arrs[i - 1])
                continue;

            // 左右双指针在arrs[i]后面的区间中寻找和为0-arrs[i]的另外两个数
            int left = i + 1;
            int right = arrSize - 1;
            while (left < right)
            {
                if (arrs[left] + arrs[right] > -arrs[i])
                    right--;    // 两数之和太大,右指针左移
                else if (arrs[left] + arrs[right] < -arrs[i])
                    left++;     // 两数之和太小,左指针右移
                else
                {
                    // 找到一个和为零的三元组,添加到结果中,左右指针内缩,继续寻找
                    rtnArr.push_back(vector<int>{arrs[i], arrs[left], arrs[right]});
                    left++;
                    right--;
                   
                    // 去重: 例如:[-4,1,1,1,2,3,3,3], i=0, left=1, right=5  第二个数和第三个数也不重复选取
                    while (left < right && arrs[left] == arrs[left - 1])  left++;
                    while (left < right && arrs[right] == arrs[right + 1])    right--;
                }
            }
        }
        return rtnArr;

    }

 

 

posted on 2023-12-04 17:46  新西兰程序员  阅读(9)  评论(0编辑  收藏  举报