3Sum问题

问题

给定一个整数数组A=[a_1,a_2,\cdots,a_n],找出数组中所有的三元组(a,b,c),满足a+b+c=0 三元组不允许重复,即对于(a_1,a_2,a_3),若p_1,p_2,p_31,2,3的一个排列数,那么(a_{p_1},a_{p_2},a_{p_3})(a_1,a_2,a_3)是重复的。
例如,(1,-5,4)(4,1,-5)属于同一个三元组,但(0,0,0)(2,2,-4)则是不同的三元组。可以把三元组认为是一个组合。

分析

一种解法是暴力解法,使用三重循环依次遍历数组,但这样时间复杂度会达到O(n^3)

先来考虑一个简单情况,即如果整数数组A是一个有序数组,例如,非降序数组,即对于数组A=[a_1,a_2,\cdots,a_n]\quad(a_i\in{\mathbb Z})a_1\leqslant a_2 \leqslant \cdots \leqslant a_n。那么可以从左往右按顺序寻找解。

例如,固定a_1,我们将在a_2,a_3,\cdots,a_n中寻找剩下的两个数a_\alpha,a_\beta,使得a_1+a_\alpha+a_\beta=0。按照这个顺序,对于a_i(i=1,2,\cdots,n-2),将在a_{i+1},a_{i+2},\cdots,a_n中寻找a_\alpha,a_\beta,使得(a_i,a_\alpha,a_\beta)成为欲求的解,其解集设为R_i(i=1,2,\cdots,n-2),即R_i=\{(a_i,a_\alpha,a_\beta)| a_i+a_\alpha+a_\beta=0 ,\alpha\neq \beta, a_\alpha,a_\beta\in A[i+1:n]\}
这里,符号A[i+1:n]表示子数组[a_{i+1},\cdots,a_n]

定理1 对于非递减整数数列A的两个元素a_i,a_{i+1},若a_i=a_{i+1},则R_i \supseteq R_{i+1}

证明

对于R_i,将其内的每一个元素t=(a_i,a_\alpha,a_\beta)按照断言P:=\left[ \left( \alpha\:{\bf is}\:(i+1) \right) {\bf or} \left( \beta\:{\bf is}\:(i+1) \right)\right]进行分组。令R^+_i=\{t|P(t)=true,t\in R_i\}以及R^-_i=\{t|P(t)=false,t\in R_i\} 显然R^+_i\cup R^-_i=R_iR^+_i\cap R^-_i=\varnothing

对于R^-_i,因为其内元素的断言P为假,因此\alpha\neq (i+1)\beta\neq(i+1)。从而
R^-_i=\{(a_{i},a_\alpha,a_\beta)| a_i+a_\alpha+a_\beta=0 ,\alpha\neq \beta, a_\alpha,a_\beta\in A[i+2:n]\}
又因为a_i=a_{i+1},从而
R^-_i=\{(a_{i+1},a_\alpha,a_\beta)| a_{i+1}+a_\alpha+a_\beta=0 ,\alpha\neq \beta, a_\alpha,a_\beta\in A[i+2:n]\}
而这正是R_{i+1}的定义,即R_{i+1}=R^-_i。因此R_{i+1}\cup R^+_i=R_i所以,R_i\supseteq R_{i+1},证明完毕 ▇▇。

推论1 对于非递减整数数列A的两个元素a_i,a_{j},若i<ja_i=a_{j},则R_i \supseteq R_{j}

证明
a_{i+1},a_{i+2},\cdots,a_{j-1}是介于a_i,a_{j}中的元素,因为A是非递减整数数列,且a_i=a_{j},这必然有a_i=a_{i+1}=a_{i+2}=\cdots=a_{j-1}=a_{j}。反复使用定理1,就有R_i\supseteq R_{i+1}\supseteq R_{i+2}\supseteq \cdots \supseteq R_{j-1}\supseteq R_{j}命题得证 ▇▇。

从推论1可知,如果我们找到了R_i,如果接下来的元素值等于a_i,那么可以跳过,直到遇到第一个a_j\neq a_i,继续寻找R_j

下面这个定理说明,不同的数组元素的所生成的元组集合也不同。

定理2 对于非递减整数数列A的两个元素a_i,a_{j},若a_i\neq a_{j},则R_i \cap R_{j}=\varnothing

不妨设a_i<a_{j},则R_j中任意元组都含有a_j而不含有a_i,而R_i中任意元组都含有a_i,因此R_i \cap R_{j}=\varnothing,证明完毕 ▇▇。

这个定理在用计算机求解该问题时很有用,因为我们不需要在找到一个解时去判断该解是否已经存在,直接加入结果集合即可。

现在我们需要一个算法能求出R_i

定理3 给定非递减整数数列A,能够在有限步之内求出R_i

证明

这是一个构造性证明,证明过程即是求出R_i的算法。

考虑子列A[i:n]=[a_i,a_{i+1},\cdots,a_n],初始时,R'_i=\varnothing
如果|A[i:n]|=(n-i+1)<3,则不存在满足条件的三元组,证明结束。

否则,令\alpha=i+1\;,\beta=n,显然\beta>\alpha。设s=a_i+a_\alpha+a_\beta

如果s>0,我们需要减小a_\beta,可令\beta=\beta-1;如果s<0,我们需要增大a_\alpha,可令\alpha=\alpha+1。更新相关变量后再重新计算s,可以使得s不断向0靠近。
假设当\alpha=\alpha_0\;,\beta=\beta_0时,s=0,此时我们找到了一个解,则令R'_i=R'_i\cap\{(a_i,a_{\alpha_0},a_{\beta_0})\},以便将解添加到解集中。接下来,我们需要同步修改\alpha,\beta的值。这是因为,假设我们令\alpha=\alpha_0保持不变,而修改参数\beta,则当且仅当\beta=\beta_0s=0,但是这个解已经存在了。我们还必须注意到,如果a_{\alpha_0}=a_{\alpha_0+1}\;,a_{\beta_0}=a_{\beta_0-1},则得到的三元组(a_i,a_{\alpha_0+1},a_{\beta_0-1})也是重复的。这启发我们可以使用如下的修改策略:找到最小正整数m,使得a_{\alpha_0}=a_{\alpha_0+1}=\cdots=a_{\alpha_0+m-1}<a_{\alpha_0+m} 以及a_{\beta_0-m}<a_{\beta_0+m+1}=\cdots=a_{\beta_0-1}=a_{\beta_0} ,从而令\alpha=\alpha+m\;,\beta=\beta-m,继续寻找下一组解。

由于初始时\beta>\alpha,且在寻找解的过程中\alpha不断增加,\beta不断减少,因此在有限步后必然会出现\alpha\geqslant\beta,此时算法停止。我们还要证明,此时的R'_i就是所求的解集R_i

显然R'_i\subseteq R_i。假设R_i中还有一组解(a_i,a_{\alpha'},a_{\beta'})\not\in R'_i,不妨设\alpha'<\beta'。在算法停止之前始终有\beta>\alpha,且在算法停止时\beta\leqslant\alpha,这就意味着在某一时刻有\alpha=\alpha',但此时\beta>\beta'或者\beta<\beta',否则解就在R'_i中。
如果\beta>\beta',那么s=a_i+a_{\alpha'}+a_{\beta}>a_i+a_{\alpha'}+a_{\beta'}=0,根据算法,此时要使\beta=\beta-1,如果此时s=a_i+a_{\alpha'}+a_{\beta}>0,就不断使\beta=\beta-1,这样一定可以使得\beta=\beta',因此解(a_i,a_{\alpha'},a_{\beta'})\in R'_i
另一方面,如果\beta<\beta',说明在之前某个时刻曾有\beta=\beta'\;,\alpha<\alpha'(因为当前时刻\alpha=\alpha',而\alpha不可能减少,所以此前时刻\alpha\leqslant\alpha',但如果等号成立则与条件矛盾,因此只取小于号。),于是s=a_i+a_{\alpha}+a_{\beta'}<a_i+a_{\alpha'}+a_{\beta'}=0,根据算法就要让\alpha=\alpha+1,不断重复就一定可以使得\alpha=\alpha',因此解(a_i,a_{\alpha'},a_{\beta'})\in R'_i

因为无论\beta>\beta'或者\beta<\beta'都会产生矛盾,所以R_i中不存在解(a_i,a_{\alpha'},a_{\beta'})\not\in R'_i,所以R'_i=R_i即为所求,证明完毕 ▇▇。

下面这个定理给出非降序序列问题的解

定理4 若给定一个非降序整数数组A=[a_1,a_2,\cdots,a_n]\;(a_i\leqslant a_j,{\rm if}\;i<j),满足a+b+c=0的所有不重复三元组(a,b,c)的集合R(A)R(A)=\cup_{i={\bf unidx}(A)}R_i 其中{\bf unidx}(A)给出A中不重复元素的下标序列[i_1,i_2,\cdots,i_k],使得对于i_p< i_q,有 a_{i_p}=a_{i_p+1}=\cdots=a_{i_q-1}<a_{i_q}。特别的,a_{i_1}=a_1\;,a_{i_k}=a_{i_k+1}=\cdots=a_n。例如,对于A=[-5,-4,-4,0,0,2,3,1,1,1]{\bf unidx}(A)=[1,2,4,6,7,8]

该定理使用推论1和定理2、定理3容易证明 ▇▇。

代码

使用JavaScript代码进行实现

/**
 * @param {number[]} nums
 * @return {number[][]}
 */
var threeSum = function(nums) {
    const LENGTH=nums.length;
    if(LENGTH<3) return [];
    nums.sort((a,b)=>a-b); 
    let result = [],ai=nums[0],j=1,k=LENGTH-1,jksum=0,lastAi=undefined,lastAj=undefined,lastAk=undefined;
    for (let i = 0; i < LENGTH - 2; lastAj=lastAk=undefined,++i,j=i+1,k=LENGTH-1,ai=nums[i]) {
        if(lastAi===ai) continue;
        while(j<k){
            if(lastAj===nums[j]&&lastAk===nums[k]){j++,k--;continue;}
            if((jksum=nums[j]+nums[k])===-ai){
                lastAj=nums[j],lastAk=nums[k],lastAi=ai;
                result.push([ai,nums[j],nums[k]]);
                j++,k--;
            }else{ jksum>-ai/*ai+aj+ak>0*/?k--:j++;}
        } 
    }
    return result;
};

//test cases


[
    [-5, -5, -4, -4, -4, -2, -2, -2, 0, 0, 0, 1, 1, 3, 4, 4]
].forEach((v,i)=>{
    console.log(`for the ${i}th case ${showArrayDeeply(v)}, your output is ${showArrayDeeply(threeSum(v))}`);
});

//helper method
function showArrayDeeply(arr){
    var result=[];
    (function show(arr,jar){
        let subjar=[]
        for(let ele of arr){
            if(Array.isArray(ele)) show(ele,subjar);
            else subjar.push(ele+"");
        }
        if(subjar.length>0) jar.push('['+subjar.join(',')+']');
    })(arr,result);
    return '['+result.join(',')+']';
};