在Gayle Laakman的书“Cracking the Coding Interview”,第六章(Big O),例12中,问题表明给定以下用于计算字符串排列的Java代码,需要计算代码的复杂性
public static void permutation(String str) {
permutation(str, "");
}
public static void permutation(String str, String prefix) {
if (str.length() == 0) {
System.out.println(prefix);
} else {
for (int i = 0; i < str.length(); i++) {
String rem = str.substring(0, i) + str.substring(i + 1);
permutation(rem, prefix + str.charAt(i));
}
}
}
这本书假设既然会有n!排列,如果我们认为每个排列都是调用树中的一个叶子,其中每个叶子都附加到长度为n的路径上,那么就不会有n * n!树中的节点(即:调用次数不超过n * n!)。
但节点的数量不应该是:
因为调用次数相当于节点数量(请看一下视频Permutations Of String | Code Tutorial by Quinston Pimenta中的数字)。
如果我们遵循这种方法,节点数将为1(树的第一级/根)+ 3(第二级)+ 3 * 2(第三级)+ 3 * 2 * 1(第四/底层)
即:节点数= 3!/ 3! + 3!/ 2! + 3!/ 1! + 3!/ 0! = 16
但是,根据上述方法,节点数将为3 * 3! = 18
我们不应该将树中的共享节点计为一个节点,因为它们表示一个函数调用吗?
答案 0 :(得分:5)
你对节点的数量是正确的。该公式给出了确切的数字,但书中的方法计算了多次。
对于大e * n!
,您的总和似乎也接近n
,因此可以简化为O(n!)
。
说调用次数不超过n * n!
在技术上是正确的,因为这是一个有效的上限。根据使用方法的不同,这可能会很好,也可能更容易证明。
对于时间复杂度,我们需要乘以每个节点的平均工作量。
首先,检查字符串连接。每次迭代都会创建2
个新字符串以传递给下一个节点。一个字符串的长度增加1
,另一个字符串的长度减少1
,但总长度始终为n
,时间复杂度为O(n)
每次迭代。
每个级别的迭代次数各不相同,因此我们不能只乘以n
。而是查看整个树的迭代总数,并获得每个节点的平均值。使用n = 3
:
1
节点迭代3
次:1 * 3 = 3
3
个节点迭代2
次:3 * 2 = 6
6
个节点迭代1
时间:6 * 1 = 6
迭代总数为:3 + 6 + 6 = 15
。这与树中的节点数大致相同。因此,每个节点的平均迭代次数是不变的。
总的来说,我们有O(n!)
个迭代,每个迭代都O(n)
工作,总时间复杂度为O(n * n!)
。
答案 1 :(得分:0)
根据您的视频,我们的字符串包含3个字符(ABC
),排列数为6 = 3!
,而6
恰好等于1 + 2 + 3
。但是,如果我们有一个包含4个字符(ABCD
)的字符串,则排列的数量应为4 * 3!
,因为D
可以位于1到4的任何位置。每个位置{ {1}}您可以为其余部分生成D
个排列。如果您重新绘制树并计算排列数,您将看到差异。
根据你的代码,我们有3!
个排列,但在每个排列调用中,你也会运行一个从0到n-1的循环。因此,您有n! = str.length()!
。
更新以回应编辑过的问题
首先,在编程中,我们经常有O(n * n!)
或0->n-1
而不是1->n
。
其次,在这种情况下,我们不计算节点的数量,就像再次查看剪辑中的递归树一样,您将看到重复的节点。在这种情况下的排列应该是彼此独特的叶子数量。
例如,如果您有一个包含4个字符的字符串,则叶子的数量应为0->n
,它将是排列的数量。但是,在您的代码段中,每个排列中都有一个4 * 3! = 24
循环,因此您需要对循环进行计数。因此,在这种情况下,您的代码复杂度为0->n-1 = 0->3
。