Leetcode 15 / LintCode 57: 3Sum

本文详细解析了一种寻找数组中三个数加和为零的有效算法,并通过实例演示如何排除重复解,确保结果唯一性。文章提供了多种实现方式,包括使用循环、条件判断及集合数据结构来优化算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这题其实不容易调通,难就难在对重复元素的排除上。比如说a[]={-2,0,0,2,2},只能输出一个[-2,0,2],但对于a[]={-4,-1,-1,0,1,2,3},必须要输出2个: [-1,-1,2]和[-1,0,1],而对于a[]={0,0,0,0},又只能输出一个[0,0,0]。

注意:

1) 此题不存在O(nlogn)的解法。我一开始设想了一个解法是用两个指针p1,p2,一前一后。假设两个指针对应数和为sum,中间那个数用binary search找-sum。如果找不到,并且sum>0,就p2--,否则p1++。

比如说,a[]={-7,-4,0,1,1,3},-7+3=-4找4找不到,p1++,-4+3=-1,找到了。

但这种方法是不对的。举个反例,a[]={-7,-4,-4,0,1,1,2,8},-7+8=1,此时要p2--,但p2不能动,因为-4-4+8=0。这个binary search的方法不对的原因我认为是:如果找不到解,不能确定p1和p2怎么动,有可能只p1++,也可能只p2--,也可能p1++和p2--都要进行,所以这种试图两边往中间移动的思路不对。

2) 正解的方法是只锁定一边,也就是i++,然后从i+1到nums.size()-1里面用p1,p2,先算sum2=nums[p1]+nums[p2],然后sum2和-nums[i]比较,若sum2>-nums[i],则p2--; 若sum2<-nums[i],则p1++;如果两者相等,说明找到一个解。要注意此时应该再加一个while循环,把所有的解都弄出来。

这两行非常重要,里面又是两个小循环:

       while ((++p1<p2) && (nums[p1]==nums[p1-1]));
       while ((p1<--p2) && (nums[p2]==nums[p2+1]));

它们表示将p1,p2移到不重复的地方,假设nums[1]=-1, p1~p2对应[-1,-1,0,1,2],此时找到一个解[-1,-1,2]。第一个while会将数组变成[-1,0,1,2],第二个while会将数组变成[-1,0,1],然后while(p1<p2)循环又会发现另一个解[-1,0,1]。注意这两个解都是i=1时发现的。

但如果input数组是[-2,0,0,2,2,2,2]时,一开始while(p1<p2)的循环内会发现[-2,0,2]这个解,过完上面两个循环数组会变成[0,0,2],所以不会有重复解出现。

我一开始将上面两个循环写成

       while ((p1<p2) && (nums[p1]==nums[p1-1])) p1++;
       while ((p1<p2) && (nums[p2]==nums[p2+1])) p2--;
这是不对的,因为第2个循环的(nums[p2]==nums[p2+1])不满足,所以p2不会--。也就是说,此时应该先无条件p1++,p2--,然后再看p1和p2的相邻元素是否一致。


3) 最开始的 if ((i==0)||(nums[i]!=nums[i-1]))也可以防止重复解出现。如果nums[i-1]和nums[i]都是解的一部分也没关系,上次i-1的时候就已经输出了(因为已经i++),这是可以直接跳过nums[i]。

4) 此题避免重复解也可以用set或map。

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

vector<vector<int> > threeSum(vector<int>& nums) {
    int i=0, p1=0, p2=0;
    vector<vector<int> > sol;

    if (nums.size()==0) return sol;

    sort(nums.begin(), nums.end());

    while(i<nums.size()-1) {

      if ((i==0)||(nums[i]!=nums[i-1])) {
        p1=i+1; p2=nums.size()-1;
        while(p1<p2)  {  //target is -nums[i]
            int sum2=nums[p1]+nums[p2];
            if (sum2>-nums[i])
                p2--;
            else if (sum2<-nums[i])
                p1++;
            else {
                //find one solution
                vector<int> tmpV;
                tmpV.push_back(nums[p1]); tmpV.push_back(nums[p2]); tmpV.push_back(nums[i]);
                sol.push_back(tmpV);
                cout<<" ["<<nums[p1]<<", "<<nums[p2]<<", "<<-sum2<<"]"<<endl;
                while ((++p1<p2) && (nums[p1]==nums[p1-1]));   //Attention, ++p1
                while ((p1<--p2) && (nums[p2]==nums[p2+1]));   //Attention, --p2
            }
         }
      }
      i++;
    }

    return sol;
}

int main()
{

    vector<int> S1={-1, 0, 1, 2, -1, -4};
    threeSum(S1);
    cout<<endl;

    vector<int> S2={0,0,0,0};
    threeSum(S2);
    cout<<endl;

    vector<int> S3={-2,0,1,1,2};
    threeSum(S3);
    cout<<endl;

    vector<int> S4={-2,0,0,2,2};
    threeSum(S4);
    cout<<endl;

    vector<int> S5={-2,0,1,1,1,2};
    threeSum(S5);
    cout<<endl;

    vector<int> S6={-3,1,2,2,2,2};
    threeSum(S6);
    cout<<endl;

    vector<int> S7;
    threeSum(S7);
    cout<<endl;

    return 0;
}

又做了一遍,第二次的代码如下,其实跟第一次差不多。

    vector<vector<int>> threeSum(vector<int> &numbers) {
        vector<vector<int>> result;
        if (numbers.size()<3) return result;
        int p1=0, p2=0, p3=0;
        sort(numbers.begin(), numbers.end());
        for (p3=2; p3<numbers.size(); ++p3) {
            if ((p3<numbers.size()-1) && (numbers[p3]==numbers[p3+1])) continue;
            p1=0; p2=p3-1;
            while(p1<p2) {
                int sum=numbers[p1]+numbers[p2];
                if (sum+numbers[p3]==0) {
                    vector<int> temp={numbers[p1],numbers[p2],numbers[p3]};
                    result.push_back(temp);
                    while(p1<p2 && (numbers[p1++]==numbers[p1]));
                    while(p1<p2 && (numbers[p2--]==numbers[p2]));
                } else if (sum+numbers[p3]<0) {
                    p1++;
                } else {
                    p2--;
                }
            }
        }
        
        return result;
    }

3刷,发现自己以前水平太次了。

class Solution {
public:
    /**
     * @param numbers: Give an array numbers of n integer
     * @return: Find all unique triplets in the array which gives the sum of zero.
     */
    vector<vector<int>> threeSum(vector<int> &numbers) {
        int n = numbers.size();
        if (n < 3) return {{}};
        sort(numbers.begin(), numbers.end());
        int p1 = 0, p2 = 0, p3 = 2;
        set<vector<int>> s;
        vector<vector<int>> result;
        while(p3 < n) {
            p1 = 0; p2 = p3 - 1;
            int target = -numbers[p3];
            while(p1 < p2) {
                int sum = numbers[p1] + numbers[p2];
                if (sum == target) {
                    s.insert({numbers[p1], numbers[p2], numbers[p3]});
                    p1++; p2--;
                } else if (sum > target) {
                    p2--;
                } else {
                    p1++;
                }
            }
            p3++;
        }
        
        result.assign(s.begin(), s.end());
        return result;
    }
};

4刷
 

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        int len = nums.size();
        if (len < 3) return result;
        sort(nums.begin(), nums.end());
        for (int i = 2; i < len; i++) {
            int start = 0, end = i - 1, target = -nums[i];
            if (i < len - 1 && nums[i] == nums[i + 1]) continue;
            while (start < end) {
                int sum = nums[start] + nums[end];
                if (sum > target) {
                    end--;
                } else if (sum < target) {
                    start++;
                } else {
                    result.push_back({nums[start], nums[end], nums[i]});
                    start++; end--;
                    while (start < end && nums[start] == nums[start - 1]) start++; //注意这里是start - 1
                    while (start < end  && nums[end] == nums[end + 1]) end--; //注意这里是end + 1
                }
            }
        }
        return result;
    }
};

代码同步在
GitHub - luqian2017/Algorithm: LintCode, LeetCode, Uva, etc

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值