我认为你得到了正确的答案,但原因是错误的。递归调用的数量与它没有任何关系。当你进行递归调用时,它会在堆栈中增加一定的空间;但是当该调用退出时,堆栈空间被释放。所以假设你有这样的东西:
void method(int n) {
if (n == 1) {
for (int i = 0; i < 10000; i++) {
method(0);
}
}
}
method(1);
虽然method 调用自身10000 次,但在任何时候堆栈上仍然不会超过2 次method 调用。所以空间复杂度将是 O(1) [常数]。
您的算法具有空间复杂度 O(n2) 的原因是 word 字符串。当n 降至0 时,将有len 堆栈条目被generateRecurse 的调用占用。最多会有len个栈入口,所以栈的空间使用量只会是O(n);但是这些堆栈条目中的每一个都有自己的word,它们都将同时存在于堆上;而那些word参数的长度是1、2、...、len,当然do加起来就是(len * (len+1)) / 2,也就是说空间使用量是O(n 2)。
关于堆栈框架的更多信息:看来,对堆栈框架的基础知识的解释会有所帮助...
“堆栈帧”只是作为“堆栈”一部分的内存区域。通常,堆栈是预定义的内存区域;但是,堆栈帧的位置和大小不是预定义的。当一个程序第一次执行时,堆栈上不会有任何东西(实际上,那里可能会有一些初始数据,但假设什么都没有,只是为了让事情变得简单)。所以内存的栈区是这样的:
bottom of stack top of stack
------------------------------------------------------------------
| nothing |
------------------------------------------------------------------
^
+--- stack pointer
(假设堆栈向上增长,从低地址到高地址。许多机器的堆栈向下增长。为简化起见,我将继续假设这是一台堆栈向上增长的机器。)
当调用方法(函数、过程、子例程等)时,会分配堆栈的某个区域。该区域足以保存方法的局部变量(或对它们的引用)、参数(或对它们的引用)、一些数据,以便程序在您return 时知道返回到哪里,以及可能的其他信息——其他信息高度依赖于机器、编程语言和编译器。在 Java 中,第一个方法是 main
bottom of stack top of stack
------------------------------------------------------------------
| main's frame | nothing |
------------------------------------------------------------------
^
+--- stack pointer
请注意,堆栈指针已向上移动。现在main 打电话给method1。由于method1 将返回到main,因此必须保留main 的局部变量和参数,以供main 恢复执行时使用。在堆栈上分配了一个新的帧,具有一定的大小:
bottom of stack top of stack
------------------------------------------------------------------
| main's frame | method1's frame | nothing |
------------------------------------------------------------------
^
+--- stack pointer
然后method1 调用method2:
bottom of stack top of stack
------------------------------------------------------------------
| main's frame | method1's frame | method2's frame | nothing |
------------------------------------------------------------------
^
+--- stack pointer
现在method2 回来了。 method2 返回后,其参数和局部变量将不再可访问。因此,可以将整个框架扔掉。这是通过将堆栈指针移回原来的位置来完成的。 (“前一个堆栈指针”是保存在某个帧中的东西之一。)堆栈回到看起来像这样:
bottom of stack top of stack
------------------------------------------------------------------
| main's frame | method1's frame | nothing |
------------------------------------------------------------------
^
+--- stack pointer
这意味着,此时,机器会将堆栈中以堆栈指针开头的部分视为“未使用”。说method2 的框架被重用是不正确的。你不能真正使用已经不复存在的东西,method2 的框架不再存在。从概念上讲,堆栈中只有一个很大的空白区域。如果method1 调用另一个方法,无论是method2、method1 递归、System.out.println 还是其他方法,都会在堆栈指针现在指向的位置创建一个新帧。此框架的大小可以小于、等于或大于过去的 method2 框架。它将占用method2 帧所在的部分或全部内存。如果是对method2 的另一个调用,则使用相同或不同的参数调用它都没有关系。没关系,因为程序不记得上次使用了什么参数。它只知道以堆栈指针开头的内存区域是空的并且可以使用。该程序不知道最近住在那里的框架。那个框架不见了,不见了,不见了。
如果你能遵循这一点,你可以看到,在计算空间复杂度时,当只查看堆栈使用的空间量时,唯一重要的是,堆栈上可以存在多少帧时间点?过去可能存在但不再存在的帧与计算无关,无论使用什么参数调用方法。
(P.S. 万一有人打算指出我在这个或那个细节上的技术错误——我已经知道这过于简单化了。)