Java - 时间复杂度 O(N**2)
Posted
技术标签:
【中文标题】Java - 时间复杂度 O(N**2)【英文标题】:Java - Time Complexity O(N**2) 【发布时间】:2022-01-20 13:26:26 【问题描述】:我正在练习 Codility。有一个简单级别的问题,但我被困在性能上。测试结果分析将此代码标记为O(N**2),但显然没有任何嵌套循环。谁能告诉我为什么我的代码是 O(N**2)?
public static int solution(int X, int[] A)
List<Integer> temp = new ArrayList<>();
for (int i = 1; i <= X; i++ )
temp.add(i);
int counter = 0;
int res = -1;
for (int i: A)
if (temp.contains(i))
temp.remove(new Integer(i));
if (temp.size() == 0)
res= counter;
break;
counter++;
if (temp.size() != 0)
res = -1;
return res;
【问题讨论】:
您可能需要检查documentation forArrayList
:“[add
除外,所有其他操作都以线性时间运行(粗略地说)”您已致电 contains
和remove
在循环中。
我认为这是由于使用了remove
和contains
。在最坏的情况下,它们将花费整个数组的遍历。
【参考方案1】:
那是因为使用了contains
。 temp
是 ArrayList
,contains
执行线性查找。在 for 循环中,这将变为 O(N2)。
【讨论】:
这个结论取决于X
,temp
的初始长度,保证与A.length
成正比。如果没有这样的约束,渐近复杂度可能或多或少,我们可以肯定地说是 O(X * N)。【参考方案2】:
测试结果分析将此代码标记为O(N**2),但显然 没有任何嵌套循环。谁能告诉我为什么我的代码是 O(N**2)?
渐近复杂度不(仅)与循环计数有关。您对A
的元素有一个循环,并在其中调用temp.contains()
,并有条件地调用temp.remove()
。对于ArrayList
,每一项的成本都与temp
中的元素数量成正比。
总体而言,如果N
是A.length
,那么您的方法的渐近复杂度为 O(X * N)。 Codility 的分析似乎不太正确,但如果不能将X
视为由常数限制,那么您的代码仍然比O(N)
更复杂。如果 Codility 执行启发式分析,在 X
和 N
之间引入人为关系,然后服从该函数关系,您的方法确实可能是 O(N2 )。
你绝对可以做得更好。您的方法似乎是计算 A
的最小初始子数组的长度,该子数组包含 1 和 X
之间的所有整数。为了有效地执行此操作,您确实需要某种机制来跟踪已看到的值,但包含在 List
中是一个昂贵的选择。请考虑使用 boolean
的数组来跟踪已看到哪些特定值,并单独计算已看到的数量:
boolean[] seen = new boolean[X + 1];
int numSeen = 0;
有了这个,循环遍历A
的元素,并且对于在1 ... X 范围内的每个元素i
,测试seen[i]
(每次测试花费O(1))。如果true
,什么也不做。如果false
,将seen[i]
设置为true
(O(1)),递增numSeen
(O(1)),并测试numSeen是否达到X
(O(1))。返回在numSeen
达到X
之前必须检查的元素数,如果numSeen
从未达到X
,则返回-1。 (细节留作练习。)
这样,每次循环迭代都会执行O(1)
工作,而不管X
上的任何限制,而O(1)
工作实际上是便宜,这是一个不同的考虑因素。总体而言:O(N),效率也很高。
【讨论】:
【参考方案3】:另一个循环隐藏在ArrayList
的remove方法中。 ArrayList
的remove方法是O(N),因为它必须移动元素来填补空白。
for (int i: A) // ===> O(N)
if (temp.contains(i))
temp.remove(new Integer(i)); //===> O(N)
if (temp.size() == 0)
res= counter;
break;
counter++;
【讨论】:
以上是关于Java - 时间复杂度 O(N**2)的主要内容,如果未能解决你的问题,请参考以下文章