问题
给定一个整数数组,找出数组中所有的三元组
,满足
三元组不允许重复,即对于
,若
是
的一个排列数,那么
与
是重复的。
例如,和
属于同一个三元组,但
和
则是不同的三元组。可以把三元组认为是一个组合。
分析
一种解法是暴力解法,使用三重循环依次遍历数组,但这样时间复杂度会达到。
先来考虑一个简单情况,即如果整数数组是一个有序数组,例如,非降序数组,即对于数组
有
。那么可以从左往右按顺序寻找解。
例如,固定,我们将在
中寻找剩下的两个数
,使得
。按照这个顺序,对于
,将在
中寻找
,使得
成为欲求的解,其解集设为
,即
这里,符号表示子数组
。
定理1 对于非递减整数数列
的两个元素
,若
,则
。
证明
对于,将其内的每一个元素
按照断言
进行分组。令
以及
显然
且
。
对于,因为其内元素的断言
为假,因此
且
。从而
又因为,从而
而这正是的定义,即
。因此
所以,
,证明完毕 ▇▇。
推论1 对于非递减整数数列
的两个元素
,若
且
,则
。
证明
设是介于
中的元素,因为
是非递减整数数列,且
,这必然有
。反复使用定理1,就有
命题得证 ▇▇。
从推论1可知,如果我们找到了,如果接下来的元素值等于
,那么可以跳过,直到遇到第一个
,继续寻找
。
下面这个定理说明,不同的数组元素的所生成的元组集合也不同。
定理2 对于非递减整数数列
的两个元素
,若
,则
。
不妨设,则
中任意元组都含有
而不含有
,而
中任意元组都含有
,因此
,证明完毕 ▇▇。
这个定理在用计算机求解该问题时很有用,因为我们不需要在找到一个解时去判断该解是否已经存在,直接加入结果集合即可。
现在我们需要一个算法能求出。
定理3 给定非递减整数数列
,能够在有限步之内求出
证明
这是一个构造性证明,证明过程即是求出的算法。
考虑子列,初始时,
。
如果,则不存在满足条件的三元组,证明结束。
否则,令,显然
。设
。
如果,我们需要减小
,可令
;如果
,我们需要增大
,可令
。更新相关变量后再重新计算
,可以使得
不断向
靠近。
假设当时,
,此时我们找到了一个解,则令
,以便将解添加到解集中。接下来,我们需要同步修改
的值。这是因为,假设我们令
保持不变,而修改参数
,则当且仅当
时
,但是这个解已经存在了。我们还必须注意到,如果
,则得到的三元组
也是重复的。这启发我们可以使用如下的修改策略:找到最小正整数
,使得
以及
,从而令
,继续寻找下一组解。
由于初始时,且在寻找解的过程中
不断增加,
不断减少,因此在有限步后必然会出现
,此时算法停止。我们还要证明,此时的
就是所求的解集
。
显然。假设
中还有一组解
,不妨设
。在算法停止之前始终有
,且在算法停止时
,这就意味着在某一时刻有
,但此时
或者
,否则解就在
中。
如果,那么
,根据算法,此时要使
,如果此时
,就不断使
,这样一定可以使得
,因此解
。
另一方面,如果,说明在之前某个时刻曾有
(因为当前时刻
,而
不可能减少,所以此前时刻
,但如果等号成立则与条件矛盾,因此只取小于号。),于是
,根据算法就要让
,不断重复就一定可以使得
,因此解
。
因为无论或者
都会产生矛盾,所以
中不存在解
,所以
即为所求,证明完毕 ▇▇。
下面这个定理给出非降序序列问题的解
定理4 若给定一个非降序整数数组
,满足
的所有不重复三元组
的集合
为
其中
给出
中不重复元素的下标序列
,使得对于
,有
。特别的,
。例如,对于
,
。
该定理使用推论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(',')+']';
};











网友评论