首页 > 代码库 > Leetcode:3Sum
Leetcode:3Sum
题目大意是传入一个整数数组A,计算所有不同的t={A[i],A[j],A[k]},使得i,j,k互异,且A[i]+A[j]+A[k]=0。
我的思路:
定义:称满足条件的{A[i],A[j],A[k]}为零化三元组,且{i,j,k}称为{A[i],A[j],A[k]}的索引,而若{A[i],A[j],A[k]}为零化三元组,则{i,j,k}也对应称为零化索引。显然不同的{A[i],A[j],A[k]}对应不同的{i,j,k}。若t=min{i, j, k},则称{i, j, k}为t基零化索引,对应的若A[t]=min{A[i],A[j],A[k]},{A[i],A[j],A[k]}称为A[t]基零化三元组。
首先利用归并排序对数组A进行排序,花费O(nlog2n)的时间,n为A的长度。这样我们就可以假定后续出现的数组A始终是以递增顺序排列的。
对于任意零化索引{i,j,k},不妨假设i<j<k。我们对i进行枚举(0~n-3)。在每次枚举中,用l和r分别记录左右边界,l的初始值为i+1,r的初始值为n-1。显然在初始的情况下,可以确保所有i基零化索引{i, j, k}中,j和k必定处于[l,r]中。假设我们已知所有可能的i基零化索引{i, j, k}或者已经被我们发现,或者j和k还处于[l,r]中,那么根据s=A[i]+A[l]+A[r]的符号,我们可以重新确定l和r的值,并保证所有i基零化索引或者已经被我们发现,或者还处于[l,r]中:
1.s < 0,则l = l + 1
2.s > 0, 则r = r - 1
3.s == 0, 则l = l + 1, r = r - 1
对上面的结论做简单说明,当s<0时,若j == l,则必然对任意l<=t<=r,有A[i]+A[l]+A[t]<=A[i]+A[l]+A[r]<0,故能保证[l,r]不存在任何j=l的i基零化索引{i, j, k}。因此所有未被发现的存在于[l,r]中的j和k必定满足j>l,即处于[l+1,r]中。对于情况2可以做相同分析。最后对于情况三,我们就发现了一个零化索引,而为了发现更多的零化索引,我们需要修改l和r(之所以可以同时修改l和r,是因为A[i]+A[j]+A[k]=0->A[k]=-A[i]-A[k]->A[k]=-A[i]-A[j],即三元组中任意两个元素可以决定第三个元素,因此可以保证新的三元组必然同不可能会出现j=l或k=r的情况)。
result = empty-list
for(i = 0; i < n - 2; i = i + 1)
if(i > 0 && A[i] == A[i - 1])
continue
l = i + 1, r = n - 1
while(l < r)
sum = A[i] + A[l] + A[r]
if(sum < 0)
l = l + 1
else if(sum > 0)
r = r - 1
else
insert tuple(A[l], A[j], A[r]) into result
for(l = l + 1; l < r && A[l] == A[l - 1]; l++)
for(r = r - 1; l < r && A[r] == A[r + 1]; r--)
上面代码加上if(i > 0 && A[i] == A[i - 1]) continue以及for(l = l + 1; l < r && A[l] == A[l - 1]; l++)是为了跳过重复的零化三元组。一个零化三元组{A[i],A[j],A[k]}可以由其较小的两个元素的值所唯一决定,即A[i]和A[j]的值。在i值不变的情况下,利用for(l = l + 1; l < r && A[l] == A[l - 1]; l++)可以保证下一个筛选出来的A[i]基零化三元组的第二个元素与前面筛选出来的三元组不同,而由A[i]不变和A[j]变化可以保证所有的在i阶段选出的A[i]基零化三元组都互不相同。而当基不同时,显然零化三元组也是不同的,因此保证了本段代码中选出的零化三元组都是互不相同的。那么如何保证在选择跳过一部分索引基时,不会遗漏一些零化三元组呢。对于任意t>i,且A[i]=A[t],由于以基i的零化索引{i,j,k}中j和k都包含在[i+1,n-1]中,而前面也已经说明过了所有的不同的零化三元组都已经被筛选了出来,而基A[t]零化三元组中的j和k都包含在[t+1,n-1]中,考虑到[t+1,n-1]是[i+1,n-1]的子集,而A[t]+A[j]+A[k]=0->A[i]+A[j]+A[k]=0,因此能保证这些三元组都已经在对i做处理时都筛选出来了。
这段代码分成内外两块迭代区域,外部for循环总共执行n-2次(定性),而内部的while循环,每次循环都必定会使得r-l至少减少1,而r-l>0,因此内部迭代发生次数必定不会超过n次,因此这段代码的总的时间复杂度为O(nlogn)+O(n-2)*O(n)=O(n^2)。
Leetcode:3Sum