3 和替代方法

3-sum alternative approach

本文关键字:方法      更新时间:2023-10-16

我尝试了 3sum 问题的替代方法:给定一个数组,找到所有总和为给定数字的三元组。

基本上方法是这样的:对数组进行排序。一旦选择了一对元素(比如 A[i] 和 A[j](,就会 [使用 equal_range 函数] 对第三个元素进行二分搜索。最后一个匹配元素之后的索引保存在变量"c"中。由于 A[j+1]> A[j],我们只搜索索引 c 并排除索引 c(因为索引 c 及以上的数字总和肯定会大于目标总和(。对于 j=i+1 的情况,我们将结束索引保存为 'd' 并使 c=d。对于 i 的下一个值,当 j=i+1 时,我们只需要搜索最多并排除索引 d。

C++实现:

int sum3(vector<int>& A,int sum)
{
int count=0, n=A.size();
sort(A.begin(),A.end());
int c=n, d=n;  //initialize c and d to array length
pair < vector<int>::iterator, vector<int>::iterator > p;
for (int i=0; i<n-2; i++)
{
for (int j=i+1; j<n-1; j++)
{
if(j == i+1)
{
p=equal_range (A.begin()+j+1, A.begin()+d, sum-A[i]-A[j]);
d = p.second - A.begin();
if(d==n+1) d--;
c=d;
}
else
{
p=equal_range (A.begin()+j+1, A.begin()+c, sum-A[i]-A[j]);
c = p.second - A.begin();
if(c==n+1) c--;
}
count += p.second-p.first;
for (auto it=p.first; it != p.second; ++it) 
cout<<A[i]<<' '<<A[j]<<' '<<*it<<'n';
}
}
return count;
}
int main()      //driver function for testing
{
vector <int> A = {4,3,2,6,4,3,2,6,4,5,7,3,4,6,2,3,4,5};
int sum = 17;
cout << sum3(A,sum) << endl;
return 0;
}

我无法计算出此算法所需的上限时间。我知道,最坏的情况是目标金额大得无法实现。

我的计算结果如下:

对于 i=0,二叉搜索的否是 lg(n-2( + lg(n-3( + ... +lg(1(

对于 i=1, lg(n-3( + lg(n-4( + ... + lg(1(

对于 i=n-3, lg(1(

所以总的来说,lg((n-2(!( + lg((n-3(!( + ... + lg(1!( = LG(1^n*2^(n-1(3^(n-2(...*(n-1(^2*n^1(

但是如何从这个表达式中推断出O(n(界呢?

除了詹姆斯的好答案之外,我还想指出,在最坏的情况下,这实际上可以上升到O (n^3),因为您正在运行 3 个嵌套的循环。考虑一下案例

{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}

要求的总和为3。

在计算复杂性时,我将首先参考 Big-O 备忘单。 我使用此工作表对代码的较小部分进行分类,以获得它们的运行时性能。

例如,如果我有一个简单的循环,它将是O(n). BinSearch(根据备忘单(是O(log(n))等。

接下来,我使用 Big-O 表示法的属性将较小的部分组合在一起。

因此,例如,如果我有两个彼此独立的循环,它将是O(n) + O(n)O(2n) => O(n)。 如果我的一个循环在另一个循环中,我会将它们相乘。 所以g( f(x) )变成了O(n^2).

现在,我知道你在说:"嘿,等等,我正在改变内循环的上限和下限",但我认为这并不重要......这是一个大学水平的例子。

所以我对你的运行时的餐巾纸后面的计算是O(n^2) * O(Log(n))O(n^2 Log(n)).

但情况不一定如此。 我本可以做一些可怕的错误。 所以我的下一步是开始绘制最坏情况的运行时。 将 sum 设置为不可能的大值并生成越来越大的数组。您可以通过使用大量重复的较小数字来避免整数溢出。

另外,将其与二次三和解决方案进行比较。这是一个已知的O(n^2)解决方案。 请务必比较最坏的情况,或者至少在两者上比较相同的数组。 同时执行两个定时测试,以便在经验测试运行时时开始感觉哪个更快。

发布版本,针对速度进行了优化。

1.对于您的分析,请注意log(1) + log(2) + ... + log(k) = Theta(k log(k)). 事实上,这个总和的上半部分是 log(k/2( + log(k/2+1( + ... + log(k(, 所以它至少是 log(k/2(*k/2,它已经与 log(k(*k 渐近相同。 同样,我们可以得出结论:

log(n-1) + log(n-2) + log(n-3) + ... + log(1) +  // Theta((n-1) log(n-1))
log(n-2) + log(n-3) + ... + log(1) +  // Theta((n-2) log(n-2))
log(n-3) + ... + log(1) +  // Theta((n-3) log(n-3))
... +
log(1) = Theta(n^2 log(n))

事实上,如果我们考虑至少是log(n/2(的对数,它是上述总和的左上象限的半三角形(因此~1/2((因此~n^2/4(,所以有Theta(n^2/8(这样的项。

2.正如 satvik 在另一个答案中指出的那样,当输出数量本身为 Theta(n^3( 时,您的输出循环可以采取高达 Theta(n^3( 步长,即当它们都相等时。

3.对于 3 和问题有 O(n^2( 解,因此它们在渐近上比这个快。