isPalindrome()的时间复杂度O()

时间:2009-08-23 13:27:59

标签: java performance big-o time-complexity

我有这个方法,isPalindrome(),我试图找到它的时间复杂度,并且还更有效地重写代码。

boolean isPalindrome(String s) {
    boolean bP = true;
    for(int i=0; i<s.length(); i++) {
        if(s.charAt(i) != s.charAt(s.length()-i-1)) {
            bP = false;
        }
    }
    return bP;
}

现在我知道这段代码检查字符串的字符是否与之前的字符相同,如果是,那么它不会改变bP。

我想我知道操作是s.length(),s.charAt(i)和s.charAt(s.length() - i - !))。

我觉得时间复杂度为O(N + 3)?这是正确的,如果不是它是什么以及如何解决。

另外为了提高效率,将字符存储在临时字符串中是否合适?

12 个答案:

答案 0 :(得分:14)

这只是O(N)。

说O(N + 3)并不真正有意义 - 忽略常数因子。

当发现不匹配时,您可以通过爆发来加快速度:

bP = false;
break;

(并不是说这会改变它的O(N),但在大多数情况下它会加速它。)

事实并非如此:

  

此代码检查字符串的字符以查看它是否与之前的字符相同

它检查开头的字符是否与最后的字符匹配,换句话说,它是一个天真的palindrome检查器。

另一个加速将循环到s.length()/2 - 否则你要对一个回文的字符串进行两次比较。

答案 1 :(得分:9)

给定代码似乎是通过检查字符“N”是否与字符“length-N”相同来检查字符串是否为回文结构。如前所述,您可以通过

提高效率
  • 只检查上半场
  • 一旦发现不匹配
  • 就会爆发(返回false)

对于这些建议,我会添加

  • 每次循环都不会重复计算s.length(),因为它不会改变。

鉴于这一切:

boolean isP(String s) {
  int l = s.length();
  int l2 = l/2;
  int j = l - 1;
  for(int i=0; i<l2; i++) {
    if(s.charAt(i) != s.charAt(j)) {
        return false;
    }
    j--;
  }
  return true;
}

答案 2 :(得分:6)

这很可能是Java中最有效的实现:

    public static boolean isP(String s) {
        char[] chars = s.toCharArray();
        for (int i = 0; i < (chars.length / 2); i++) {
            if (chars[i] != chars[(chars.length - i - 1)])
                return false;
        }
        return true;
    }

优点:

  • 第一眼看到差异的回报。
  • 使用直接char []访问以避免在charAt中完成大量检查
  • 只迭代字符串的一半,而不是整个字符串。

与所有其他提议的解决方案一样,它仍然是O(N)。

我刚刚测量了所提出的解决方案的时间非常大(以纳秒为单位):

 Aran:           32244042
 Andreas:        60787894
 Paul Tomblin:   18387532

首先,上面的测量是使用客户端VM 完成的。因此,计算i < (chars.length / 2)未作为常量内联。 使用-server Vm参数给出了更好的结果:

 Aran:           18756295
 Andreas:        15048560
 Paul Tomblin:   17187100

为了推动它有点极端:


首先警告:不要在任何您打算使用/运输的程序中使用此代码。


它包含隐藏的错误,并且不遵守Java API并且没有错误处理,正如评论中所指出的那样。它纯粹用于证明通过肮脏技巧可以获得的理论性能改进。

从字符串复制数组时会有一些开销,因为字符串类在内部会产生防御性副本。

如果我们直接从字符串中获取原始char [],我们可以挤出一些性能,代价是对字符串使用反射和unsave操作。这使我们再获得20%的表现。

public static boolean isPReflect(String s) {
    char[] chars = null;
    try {
        final Field f = s.getClass().getDeclaredField("value");
        f.setAccessible(true);
        chars = (char[]) f.get(s);
    }
    catch (IllegalAccessException e) {
    }
    catch (NoSuchFieldException e) {
    }

    final int lenToMiddle = chars.length / 2;
    for (int i = 0; i < lenToMiddle; i++) {
        if (chars[i] != chars[(chars.length - i - 1)]) 
            return false;
    }
    return true;
}

时间:

 Aran:           18756295
 Andreas1:       15048560
 Andreas2:       12094554
 Paul Tomblin:   17187100

答案 3 :(得分:3)

是O(N)。您正在进行N次比较,其中N = s.length()。每次比较都需要O(1)时间,因为它是单个字符比较。

+3并不重要,因为渐近符号只关心最高阶项。

答案 4 :(得分:3)

这是另一个有两个相反指针的解决方案:

boolean isPalindrome(String s) {
    int i = 0, j = s.length() - 1;
    while (i < j && s.charAt(i) == s.charAt(j)) {
        ++i;
        --j;
    }
    return i >= j;
}

复杂性再次 O n )。

进一步了解细节:假设每个操作都需要1个单位。比较,分配,算术运算,函数调用每个花费1个单位。因此,在最坏情况下调用isPalindrome费用(s是一个回文)需要例如:

  4 + n/2 · (3 + 4) + 1
= 5 + n/2 · 7
= 5 + 7/2 · n

由于省略了常数因子(此处为5 + 7/2),我们最终得到5 + 7/2· n O ñ)。

答案 5 :(得分:2)

首先,对于这个问题,不存在单线程解决方案,其中“最坏情况”复杂度优于任意输入字符串的O(N)。简单地说,在最坏的情况下,任何算法都必须查看字符串中的每个字符。从理论上讲,您可以使用硬件并行性来改进O(N);即,具有在字符串的不同部分上工作的无限可缩放数量的处理器。在实践中,根本不可能实现任何加速。将输入字符串(或相关部分)发送到每个处理器的成本将是“O(N)”,除非有一些我不知道的解决方案。

其次,您可以看到O(N)行为不是最终答案。您还需要将乘法常数视为N - >无穷大,以及较小的N值的较小术语。

第三,@ dfa说微观优化不是你的事。他走在正确的轨道上,但我认为它不是那么明确。 IMO,这是浪费时间微优化,除非1)你的应用程序真的需要尽可能快地运行,2)你的应用程序分析表明这个特定的计算真的< / em>是一个重要的瓶颈。

最后,微程序优化使一个特定硬件平台/ JIT编译器的程序更快,可能会使另一个更慢。复杂的微优化代码对于JIT编译器来说更难以生成有效的代码。如果你使用反射来访问(比如说)String类的内部,你的代码可能在某些平台上实际上失败了。 (Java类库规范中没有任何内容表明String有一个名为“value”的私有字段,它是char[] !!!)

答案 6 :(得分:1)

首先,该方法应该做什么?

我的猜测:确定字符串是否为palindrome

很明显,你不能在O(N)下得到它:

O(N+3) == O(N)

另一个问题是,它是最有效的解决方案吗?也许不是。

改进空间:

  1. 将它切成两半。你检查所有角色两次(如Michiel Buddingh建议的那样)。

  2. 预先获取字符数组。这使您在chatAt()内发生了一些索引检查。

  3. 所有其他操作charAt()length()都是标准字符串实现的O(1)。

答案 7 :(得分:0)

第一个改进:一旦找到不匹配就可以中断,对吧?

答案 8 :(得分:0)

你可以通过停止在(i ==(s.length()/ 2)+1)将函数的复杂性减半。它与Big O术语无关,但它仍然是一个相当不错的收获。

答案 9 :(得分:0)

Big O复杂性总是没有costants(因为对于N&gt; oo,它们并不重要)。因此,您的时间复杂度只是O(n)

  

另外为了提高效率,将字符存储在临时字符串中是否合适?

这不是你的工作。 JIT编译器将为您处理这种微优化。

答案 10 :(得分:0)

假设循环中的操作可以在恒定时间内执行,复杂度为O(N)。 由于“Big-O”符号表示增长,而不是纯粹的速度,因此可以忽略常数因素。这使我们得出O(N + 3)等于O(N)的结论。

答案 11 :(得分:0)

或者你可以简单地做

def isPalindrome?(x)
 return x == x.reverse
end

这仍然是O(n)时间复杂度。