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 for ArrayList:“[add 除外,所有其他操作都以线性时间运行(粗略地说)”您已致电 containsremove循环中。 我认为这是由于使用了removecontains。在最坏的情况下,它们将花费整个数组的遍历。 【参考方案1】:

那是因为使用了containstempArrayListcontains 执行线性查找。在 for 循环中,这将变为 O(N2)

【讨论】:

这个结论取决于Xtemp的初始长度,保证与A.length成正比。如果没有这样的约束,渐近复杂度可能或多或少,我们可以肯定地说是 O(X * N)。【参考方案2】:

测试结果分析将此代码标记为O(N**2),但显然 没有任何嵌套循环。谁能告诉我为什么我的代码是 O(N**2)?

渐近复杂度不(仅)与循环计数有关。您对A 的元素有一个循环,并在其中调用temp.contains(),并有条件地调用temp.remove()。对于ArrayList,每一项的成本都与temp 中的元素数量成正比。

总体而言,如果NA.length,那么您的方法的渐近复杂度为 O(X * N)。 Codility 的分析似乎不太正确,但如果不能将X 视为由常数限制,那么您的代码仍然比O(N) 更复杂。如果 Codility 执行启发式分析,在 XN 之间引入人为关系,然后服从该函数关系,您的方法确实可能是 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)的主要内容,如果未能解决你的问题,请参考以下文章

八大排序算法JAVA实现(时间复杂度O(n*logn)篇)

Java 数据结构 & 算法宁可累死自己, 也要卷死别人 2 时间复杂度 & 空间复杂度

归并排序 - 递归非递归实现java

Java九大排序算法总结(复杂度及应用场景)

[Java]1.两数之和 - LeetCode

Java的List去重