为什么这个O(n^2)代码的执行速度比O(n)快?

2022-09-01 04:02:19

我已经为两种方法编写了代码,以找出LeetCode上字符串中的第一个唯一字符。

问题陈述:给定一个字符串,找到其中的第一个非重复字符并返回其索引。如果它不存在,则返回 -1。

示例测试用例:

s = “leetcode” 返回 0。

s = “loveleetcode”,返回 2。

方法1(O(n))(如果我错了,请纠正我):

class Solution {
    public int firstUniqChar(String s) {

        HashMap<Character,Integer> charHash = new HashMap<>();

        int res = -1;

        for (int i = 0; i < s.length(); i++) {

            Integer count = charHash.get(s.charAt(i));

            if (count == null){
                charHash.put(s.charAt(i),1);
            }
            else {
                charHash.put(s.charAt(i),count + 1);
            }
        }

        for (int i = 0; i < s.length(); i++) {

            if (charHash.get(s.charAt(i)) == 1) {
                res = i;
                break;
            }
        }

        return res;
    }
}

方法 2 (O(n^2)):

class Solution {
    public int firstUniqChar(String s) {

        char[] a = s.toCharArray();
        int res = -1;

        for(int i=0; i<a.length;i++){
            if(s.indexOf(a[i])==s.lastIndexOf(a[i])) {
                res = i;
                break;
            }
        }
        return res;
    }
}

在方法2中,我认为复杂性应该是O(n^2),因为indexOf在这里在O(n*1)中执行。

但是当我在LeetCode上执行这两个解决方案时,我得到的运行时为19 ms,方法2的运行时为92 ms。我很困惑;为什么会这样?

我假设LeetCode针对最佳,最差和平均情况对小和大输入值进行测试。

更新:

我知道O(n^2算法)可以在某些n<n1中表现得更好。在这个问题中,我想了解为什么在这种情况下会发生这种情况。即方法1的哪个部分使其变慢。

LeetCode 链接到问题


答案 1

考虑:

  • f1(n) = n2
  • f2(n) = n + 1000

显然,f1 是 O(n2),f2 是 O(n)。对于一个小的输入(比如n=5),我们有f1(n) = 25,但f2(n)>1000。

仅仅因为一个函数(或时间复杂度)是O(n),另一个是O(n2),并不意味着前者对于n的所有值都更小,只是说有一些n以上就是这种情况。


答案 2

对于非常短的字符串,例如单个字符,创建的成本,重新调整大小,在装箱和取消装箱时查找条目可能会掩盖的成本,这可能被认为是热的,并且JVM以任何一种方式内联。HashMapcharCharacterString.indexOf()

另一个原因可能是RAM访问的成本。在查找中涉及额外的 、和对象时,可能需要对 RAM 进行额外的访问。单次访问约为 100 ns,这可以加起来。HashMapCharacterInteger

看看Bjarne Stroustrup:为什么你应该避免Linked Lists。本讲座说明了性能与复杂性不同,内存访问可能是算法的杀手。