为啥使用 2 个嵌套循环(O(n^2) 复杂度)解决两个和问题,在仅更改循环计数器逻辑时运行得更快?
Posted
技术标签:
【中文标题】为啥使用 2 个嵌套循环(O(n^2) 复杂度)解决两个和问题,在仅更改循环计数器逻辑时运行得更快?【英文标题】:Why solving the two sum problem using a 2 nested loops, O(n^2) complexity, run much faster when only changing the loops counter logic?为什么使用 2 个嵌套循环(O(n^2) 复杂度)解决两个和问题,在仅更改循环计数器逻辑时运行得更快? 【发布时间】:2019-12-02 11:33:41 【问题描述】:解决two sum 问题可以使用 O(n) 复杂度算法来实现,但是,我只是尝试了 O(n^2) 复杂度,这是使用 2 个嵌套循环检查每个 ith 之和的简单方法整数,其余整数都针对目标值,以下是 O(n^2) 实现,对于 2 个实现,nums 是整数数组,n是 nums 的大小,indices 是一个大小为 2 的数组,用于保存 2 个整数的索引
for(int i=0; i<n; ++i)
for(int j=i+1; j<n; ++j)
if(nums[i] + nums[j] == target)
indices[0] = i; indices[1] = j; return indices;
这个实现在 140 毫秒内解决了这个问题。我尝试了另一种 O(n^2) 方法,即对于从 1 到 n-1 的每个 k 值,检查第 i 个整数和第 (i+k) 个整数的和与目标值,以下是实现,
for(int k=1; k<n; k++)
for(i=0; i<n-k; i++)
int j=i+k;
if(nums[i] + nums[j] == target)
indices[0] = i; indices[1] = j; return indices;
如您所见,相同的循环体,但运行速度更快,运行时间为 8 毫秒。这是为什么?是否与空间局部性有关?
【问题讨论】:
也许你的if
代码很少被执行。因此,您通常应该使用几个不同的基准测试(而不仅仅是一个测试用例)来备份您的测量结果。
您可以查看生成的代码以了解编译器如何处理代码。 godbolt.org 之类的网站对此非常有用,因为您可以同时查看这两个变体。
请包含输入数据。或者更好的是,提供minimal reproducible example。
【参考方案1】:
公平的比较会使两个程序都执行到最后,但仍然找不到索引。从外观上看,您正在针对存在答案的案例进行测试。当然,在这种情况下,我们搜索答案的顺序非常重要。
例如,当唯一的答案是 n - 2, n - 1
时,第一个代码需要 O(n^2) 次操作才能找到它,而第二个代码需要 O(n) 才能找到它。生成代码:
std::fill (&num[0], &num[0] + n, 0);
target = 2;
num[n - 2] = 1;
num[n - 1] = 1;
相反,当唯一的答案是0, n - 1
时,第一个代码在 O(n) 中找到它,而第二个代码将花费 O(n^2) 步。生成代码:
std::fill (&num[0], &num[0] + n, 0);
target = 2;
num[0] = 1;
num[n - 1] = 1;
&num[0]
的作用是确保无论num
是数组还是向量,它都能正常工作。
【讨论】:
是的,正如你所说,这完全是关于 2 个整数的位置。当没有解决方案时,它们的运行时间几乎相同。以上是关于为啥使用 2 个嵌套循环(O(n^2) 复杂度)解决两个和问题,在仅更改循环计数器逻辑时运行得更快?的主要内容,如果未能解决你的问题,请参考以下文章