代码中的递归解决方案的时间复杂度来自: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)。非常感谢任何帮助!
答案 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
),并创建一个包含单词 a
、aa
、aaa
、...、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))
。您可以在此处看到这样的证明以及最坏的例子:
答案 3 :(得分:0)
我认为这里的复杂性的一种直观方式是,有多少种方法可以在此处的字符串中添加空格或在此处断开单词?
对于 4 个字母的单词: 没有在索引 0-1 处中断的方法 * 在索引 1-2 处没有中断方法 * 在索引 2-3 处没有中断方法 = 2 * 2 * 2。
2 表示两个选项 => 你打破它,你不打破它
O(2^(n-1)) 是分词的递归复杂度,然后 ;)