理解算法复杂度

6

我正在查看一些关于编程面试的在线算法解决方案,但我不理解为什么这个算法被称为O(n^3)。

警告:我知道在工业界中经常滥用大O符号,当我提到O(n)时,我是指算法运行时间的上限,这在学术界以外的大多数地方都很常见。

寻找最长回文子串。一个简单的解决方案可能是:

bool isPalindrome(std::string s) {
  if (s.length() <= 1) {
    return true;
  }

  if (s[0] == s[s.length() - 1]) {
    return isPalindrome(s.substr(1, s.length() - 2));
  } else {
    return false;
  }
}

std::string longestPalindrome(std::string s) {
  std::string max_pal = "";
  for (size_t i = 0; i < s.length(); ++i) {
    for (size_t len = 1; len <= s.length() - i; ++len) {
      std::string sub = s.substr(i,len);
      if (isPalindrome(sub)) {
        if (max_pal.size() < sub.size()) max_pal = sub;
      }
    }
  }
  return max_pal;
}

这个算法不是O(n^2)吗?很简单,生成所有子串需要O(n^2)的时间,并且需要O(n)的时间来确定它是否为回文。其中n是初始字符串中字符的数量。


我想知道它是否将创建子字符串视为复杂性的一部分。 - Tadhg McDonald-Jensen
生成所有子字符串需要 O(n^2) 的时间,检查每一个子字符串需要 O(n) 的时间。 - Tadhg McDonald-Jensen
我知道这不是问题的答案,但为了完整起见,这里有一个O(n^2)算法的快速想法:可能的“中心点”只有线性数量,并且通过从中间扩展每个候选项来计算其长度最多需要线性时间。虽然有偶数和奇数长度的回文串,例如ABBA与BOB。对于前者,我们选择一个在两个字符之间的中心点。最好也从字符串的中心点开始。对于包含许多相同字符的输入,这将复杂度降至O(n)。 - Arne Vogel
4个回答

8
“这个算法不是O(n^2)吗?简单来说,生成所有子串需要O(n^2)的时间,判断是否为回文需要O(n)的时间。”
“你所描述的正是O(n^3),因为对于每个子串,你都要执行一个花费O(n)的操作,所以总操作数为O(n^2 * C*n),即O(n^3)。”
然而,所描述的代码实际上是 O(n^4)isPalindrome()O(n^2):
  • 你正在创建大小为 1 + 3 + 5 + ... + n-2O(n) 子字符串,总时间复杂度为O(n^2)
  • longestPalindrome() 中执行这个步骤O(n^2)次,总时间复杂度为O(n^4)

(这假设 substr() 复杂度为 O(n)。虽然没有定义,但通常是这种情况)


为什么isPalindrome是O(n^2)?它不只检查一次字符串的每个字符吗? - John Doe
1
@JohnDoe 由于string::substr()的时间复杂度为O(n),而您正在递归地进行O(n)次操作。所创建的子字符串的总大小为1 + 3 + 5 + ... + n-2,即O(n^2) - amit
显而易见,在C++17中,一个简单的效率修复方法是将isPalindrome的参数类型更改为std::string_view,其substr具有常数复杂度。虽然我相当确定整个问题可以用O(n^2)最坏情况算法解决... - Arne Vogel

1
你几乎是正确的, 生成字符串和检查它们需要O(n^2)和O(n)次操作。 因此,你需要进行O(n^2)(字符串数量) * O(n)次检查。 由于n^2 * n = n^3,总运行时间为O(n^3)。

1
双重循环内执行O(n^2)(子字符串本身是O(n)),这使得我们的复杂度为O(n^4)。

该实现实际上是O(n^4)。 - amit
什么?怎么回事?String.substring 是 O(1),你怎么会得到 O(n^4)? - Marcin Pietraszek
1
cplusplus.com复杂度:未指定,但通常与返回的对象长度成线性关系。 我很确定在C++11中它必须是线性的,因为COW不再存在,但我不会对此进行承诺。 - amit
感谢澄清,我一直确信它以某种方式重用了底层数组。 - Marcin Pietraszek

0

实际上,由于实现的野蛮程度,这甚至会是O(N^4)。

isPalindrome的实现方式是,对于每个递归调用,它都会分配一个新字符串,该字符串基本上是源字符串去掉第一个和最后一个字符。因此,每次这样的调用已经是O(n)了。


1
我告诉你,先生,我认识一些非常有才华的野蛮人。没有人像我的老朋友野人图博格那样执行分治算法。假设他没喝醉的话。他喝得很多。 - user4581301

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接