单词时间复杂度的递归解决方案?

时间:2015-07-12 17:39:13

标签: algorithm recursion big-o time-complexity complexity-theory

代码中的递归解决方案的时间复杂度来自:http://www.geeksforgeeks.org/dynamic-programming-set-32-word-break-problem/

// returns true if string can be segmented into space separated
// words, otherwise returns false
bool wordBreak(string str)
{
    int size = str.size();

    // Base case
    if (size == 0)  return true;

    // Try all prefixes of lengths from 1 to size
    for (int i=1; i<=size; i++)
    {
        // The parameter for dictionaryContains is str.substr(0, i)
        // str.substr(0, i) which is prefix (of input string) of
        // length 'i'. We first check whether current prefix is in
        // dictionary. Then we recursively check for remaining string
        // str.substr(i, size-i) which is suffix of length size-i
        if (dictionaryContains( str.substr(0, i) ) &&
            wordBreak( str.substr(i, size-i) ))
            return true;
    }

    // If we have tried all prefixes and none of them worked
    return false;
}

我认为它的n ^ 2是因为对n方法的调用,最坏的情况是(n-1)工作(递归迭代其余的字符串?)。或者它是指数/ n!?

我很难搞清楚这些递归函数的大(O)。非常感谢任何帮助!

4 个答案:

答案 0 :(得分:4)

答案是指数,准确地说是O(2^(n-2))(2 power n-2)

在每次调用中,您调用长度为1,2....n-1的递归函数(在最坏的情况下)。要完成长度为n的工作,您将递归地完成所有长度为n-1, n-2, ..... 1的字符串的工作。所以T(n)是你当前通话的时间复杂度,你在内部做sum of T(n-1),T(n-2)....T(1)的工作。

数学上:

  T(n) = T(n-1) + T(n-2) +.....T(1);
  T(1) = T(2) = 1 

如果您真的不知道如何解决这个问题,解决上述问题的更简单方法就是替换值。

  T(1) = T(2) = 1
  T(3) = T(1) + T(2) = 1+1 =2; // 2^1
  T(4) = T(1)+ T(2) + T(3) = 1+1+2 =4; //2^2
  T(5) = T(1) + T(2) +T(3) +T(4) = 1+1+2+4 =8; //2^3

因此,如果您替换前几个值,则很明显时间复杂度为2^(n-2)

答案 1 :(得分:1)

简短版本:

<块引用>

这个函数的最坏情况运行时间是Θ(2n),这很令人惊讶,因为它忽略了每个递归调用所做的二次工作量,只是简单地拆分将字符串分成几部分并检查哪些前缀是单词。

更长的版本:假设我们有一个输入字符串,它由字母 a 的 n 个副本组成,后跟字母 b。 (我们将其缩写为 aⁿb),并创建一个包含单词 aaaaaa、...、aⁿ 的字典。< /p>

现在,递归会做什么?

  • 首先,请注意所有递归调用都不会返回 true,因为无法考虑字符串末尾的 b。这意味着每次递归调用都将调用 aᵏb 形式的字符串。让我们将处理这样一个字符串所需的时间表示为 T(k)。这些调用中的每一个都会触发 k 个较小的调用,每个 aᵏb 后缀调用一个。

  • 但是,我们还必须考虑到运行时的其他贡献者。特别是,调用 string::substr 来形成长度为 k 的子串需要时间 O(k)。我们还需要考虑检查前缀是否是单词的成本。此处未显示有关如何执行此操作的代码,但假设我们使用特里树或哈希表,我们可以假设检查长度为 k 的字符串是否为单词的成本也是 O(k)。这意味着,在我们进行递归调用的每个点上,我们将做 O(n) 工作 - 一些工作来检查前缀是否是一个单词,以及一些工作来形成与后缀对应的子字符串。

因此,我们明白了

<块引用>

T(k) = T(0) + T(1) + T(2) + ... + T(k-1) + O(k2)

这里,循环的第一部分对应于每个递归调用,循环的第二部分说明了制作每个子串的成本。 (有 n 个子串,每个子串的处理时间为 O(n))。我们的目标是解决这个循环问题,为了简单起见,我们假设 T(0) = 1。

为此,我们将使用“扩展和收缩”技术。让我们写出彼此相邻的 T(k) 和 T(k+1) 的值:

<块引用>

T(k) = T(0) + T(1) + T(2) + ... + T(k-1) + O(k2)

T(k+1) = T(0) + T(1) + T(2) + ... + T(k-1) + T(k) + O(k2)

从第二个表达式中减去第一个表达式可以得到

<块引用>

T(k+1) - T(k) = T(k) + O(k),

或者那个

<块引用>

T(k+1) = 2T(k) + O(k)。

我们是如何从两个 O(k2) 项的差异中得到 O(k) 的?这是因为 (k + 1)2 - k2 = 2k + 1 = O(k).

这是一个更容易使用的循环,因为每个术语都取决于前一个。为简单起见,我们将假设 O(k) 项实际上只是 k,给出递归

<块引用>

T(k+1) = 2T(k) + k。

这个递归求解为 T(k) = 2k+1 - k - 1。为了看到这一点,我们可以使用快速归纳论证。具体:

<块引用>

T(0) = 1 = 2 - 1 = 20+1 - 0 - 1

T(k+1) = 2T(k) + k = 2(2k - k - 1) + k = 2k+1 - 2k - 2 + k = 2k+1 - k - 2 = 2k+1 - (k + 1) - 1

因此,我们得到我们的运行时间是 Θ(2n),因为我们可以忽略低阶 n 项。

看到这一点我非常惊讶,因为这意味着每次递归调用完成的二次工作不会影响整体运行时间!在进行此分析之前,我最初会猜测运行时将类似于 Θ(n · 2n)。 :-)

答案 2 :(得分:0)

我认为答案实际上应该是O(2^(n-1))。您可以在此处看到这样的证明以及最坏的例子:

https://leetcode.com/problems/word-break/discuss/169383/The-Time-Complexity-of-The-Brute-Force-Method-Should-Be-O(2n)-and-Prove-It-Below

答案 3 :(得分:0)

我认为这里的复杂性的一种直观方式是,有多少种方法可以在此处的字符串中添加空格或在此处断开单词?

对于 4 个字母的单词: 没有在索引 0-1 处中断的方法 * 在索引 1-2 处没有中断方法 * 在索引 2-3 处没有中断方法 = 2 * 2 * 2。

2 表示两个选项 => 你打破它,你不打破它

O(2^(n-1)) 是分词的递归复杂度,然后 ;)