为啥使用 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;

&amp;num[0] 的作用是确保无论num 是数组还是向量,它都能正常工作。

【讨论】:

是的,正如你所说,这完全是关于 2 个整数的位置。当没有解决方案时,它们的运行时间几乎相同。

以上是关于为啥使用 2 个嵌套循环(O(n^2) 复杂度)解决两个和问题,在仅更改循环计数器逻辑时运行得更快?的主要内容,如果未能解决你的问题,请参考以下文章

为啥以下算法(循环排序?!)的时间复杂度是 O(n)?

汽车加油问题(贪心算法),O(n) 复杂度的嵌套 while 循环

嵌套循环的大O时间复杂度

一般三个嵌套循环的 O(n^3) 复杂度的数学推导

Python中嵌套For循环的时间复杂度

时间复杂度