3 和替代方法
3-sum alternative approach
我尝试了 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( 解,因此它们在渐近上比这个快。
- 为不同配置设置MSVC_RUNTIME_LIBRARY的正确方法是什么
- 通过方法访问结构
- 最小硬币更换问题(自上而下方法)
- C++为构建时间获取QDateTime的可靠方法
- 在C#中处理C++指针而不使用unsafe的最佳方法
- 处理多个异常集合的C++方法
- 如果C++类在类方法中具有动态分配,但没有构造函数/析构函数或任何非静态成员,那么它仍然是POD类型吗
- 有什么方法可以遍历结构吗
- 当类在C++中定义时,有什么方法可以"register"类吗?
- 在C++中,将大的无符号浮点数四舍五入为整数的最佳方法是什么
- 实现无开销push_back的最佳方法是什么
- 使用std::函数映射对象方法
- 有符号的int和int-有没有一种方法可以在C++中区分它们
- C++从另一个类访问公共静态向量的正确方法是什么
- C++优先级队列,按对象的唯一指针的特定方法升序排列
- 没有为自己的结构调用列表推回方法
- 有没有什么方法可以使用一个函数中定义的常量变量,也可以由c++中同一程序中的其他函数使用
- 在类定义之后定义一个私有方法
- 枚举环境变量的惯用C++14/C++17方法
- 初始化具有非默认构造函数的std::数组项的更好方法