Java dfs的空间复杂性
我试图分析以下算法的空间复杂度:Java dfs的空间复杂性,java,time-complexity,Java,Time Complexity,我试图分析以下算法的空间复杂度: /** * // This is the interface that allows for creating nested lists. * // You should not implement it, or speculate about its implementation * public interface NestedInteger { * // Constructor initializes an empty nested li
/**
* // This is the interface that allows for creating nested lists.
* // You should not implement it, or speculate about its implementation
* public interface NestedInteger {
* // Constructor initializes an empty nested list.
* public NestedInteger();
*
* // Constructor initializes a single integer.
* public NestedInteger(int value);
*
* // @return true if this NestedInteger holds a single integer, rather than a nested list.
* public boolean isInteger();
*
* // @return the single integer that this NestedInteger holds, if it holds a single integer
* // Return null if this NestedInteger holds a nested list
* public Integer getInteger();
*
* // Set this NestedInteger to hold a single integer.
* public void setInteger(int value);
*
* // Set this NestedInteger to hold a nested list and adds a nested integer to it.
* public void add(NestedInteger ni);
*
* // @return the nested list that this NestedInteger holds, if it holds a nested list
* // Return null if this NestedInteger holds a single integer
* public List<NestedInteger> getList();
* }
*/
class Solution {
public int depthSum(List<NestedInteger> nestedList) {
return helper(nestedList, 1);
}
public int helper(List<NestedInteger> nestedList, int depth){
if(nestedList == null || nestedList.size() == 0){
return 0;
}
int sum = 0;
for(NestedInteger list : nestedList){
if(list.isInteger()){
sum = sum + depth * list.getInteger();
}
if(list.getList() != null){
sum = sum + helper(list.getList(), depth + 1);
}
}
return sum;
}
}
/**
*//这是允许创建嵌套列表的接口。
*//您不应该实现它,也不应该猜测它的实现
*公共接口嵌套整数{
*//构造函数初始化空嵌套列表。
*公共嵌套整数();
*
*//构造函数初始化单个整数。
*公共嵌套整数(int值);
*
*//@如果此嵌套整数包含单个整数而不是嵌套列表,则返回true。
*公共布尔isInteger();
*
*//@如果嵌套整数包含单个整数,则返回该嵌套整数包含的单个整数
*//如果此嵌套整数包含嵌套列表,则返回null
*公共整数getInteger();
*
*//将此嵌套整数设置为包含单个整数。
*公共void setInteger(int值);
*
*//将此嵌套整数设置为包含嵌套列表,并向其中添加嵌套整数。
*公共无效添加(嵌套整数ni);
*
*//@如果此NestedInteger包含嵌套列表,则返回其包含的嵌套列表
*//如果此嵌套整数包含单个整数,则返回null
*公共列表getList();
* }
*/
类解决方案{
公共内部深度总和(列表嵌套列表){
返回帮助程序(嵌套列表,1);
}
公共整型帮助器(列表嵌套列表,整型深度){
if(nestedList==null | | nestedList.size()==0){
返回0;
}
整数和=0;
用于(嵌套整数列表:嵌套列表){
if(list.isInteger()){
sum=sum+depth*list.getInteger();
}
if(list.getList()!=null){
sum=sum+helper(list.getList(),深度+1);
}
}
回报金额;
}
}
空间复杂度应该是O(D),其中$D$是输入中嵌套的最大级别。为什么是这样
我的分析:根据谷歌的说法,空间复杂度是指在最坏的情况下,算法中任何一点需要多少内存。因为Java是按值传递的,所以每次调用helper函数时,我们都需要额外的空间来输入,所以我们使用的最大内存将用于第一次调用helper时,它占用的空间等于用来保存输入的空间,而该空间似乎不是O(D).既然您是通过例程分配的深度加上任何附加数据结构来确定空间复杂度,那么我同意嵌套列表上的DFS是
O(d)
,其中d
是树的最大深度。让我们看看典型树上的DFS:
a
/ \
b e
/ \
c d
DFS将以a
作为根调用递归函数:
a
a
\
e
将访问其第一个孩子:
a
/
b
在这一点上,DFS击中一片叶子并开始回溯。我们已经达到DFS将消耗的最大内存
a
/
b
下一个叶匹配但不超过树的最大深度:
a
/
b
\
d
现在访问根目录的右侧子树:
a
a
\
e
我们完成了。请记住,为每个节点创建一个调用堆栈框架,然后在访问该节点后弹出
既然我们同意在任何时刻都不超过d
堆栈帧,那么下一步就是确定单个堆栈帧的大小。如果我们确信这是O(1)
,也就是说,输入的大小对堆栈帧大小没有影响,那么我们的总体复杂性是O(d)
这个问题的答案是,尽管Java是按值传递的,“值”不是内存中列表数据结构的副本。相反,它只是对数据结构的引用。引用的大小是恒定的,因此每个帧的开销是固定的
.-------------- heap memory ---------------.
| ... [the actual list data in memory] ... |
`--------------------^---------------------`
|
+----------------+
| |
.---|- frame 0 ----. |
| nestedList sum | |
`------------------` |
|
+----------------+
| |
.---|- frame 1 ----. |
| nestedList sum | |
`------------------` |
|
+----------------+
|
.---|- frame 2 ----.
| nestedList sum |
`------------------`
上图过于简化;这些列表引用并不都指向外部列表,而是外部列表的子列表,因此实际情况更像:
.------------------ heap memory ------------------.
| ... [list] ... [list] ... [list] ... [list] ... |
`-------^------------^--------^-------------------`
| | |
+---+ | |
| | |
.---|- frame 0 ----. | |
| nestedList sum | | |
`------------------` | |
| |
+----------------+ |
| |
.---|- frame 1 ----. |
| nestedList sum | |
`------------------` |
|
+-------------------------+
|
.---|- frame 2 ----.
| nestedList sum |
`------------------`
但是,所有堆内存都是在函数运行之前分配的;函数中的任何地方都没有new
(也没有调用本身调用new
的函数)。这是一个纯遍历/搜索例程,有几个指向内存的小变量
如果您仍然不确定,可以使用内存分析器以不同的数据结构运行此代码并绘制结果。这应该给出一个与数据结构深度成比例的线性图。既然您是通过加上例程分配的任何附加数据结构的深度来确定空间复杂度,那么我同意嵌套列表上的DFS是
O(d)
,其中d
是树的最大深度。让我们看看典型树上的DFS:
a
/ \
b e
/ \
c d
DFS将以a
作为根调用递归函数:
a
a
\
e
将访问其第一个孩子:
a
/
b
在这一点上,DFS击中一片叶子并开始回溯。我们已经达到DFS将消耗的最大内存
a
/
b
下一个叶匹配但不超过树的最大深度:
a
/
b
\
d
现在访问根目录的右侧子树:
a
a
\
e
我们完成了。请记住,为每个节点创建一个调用堆栈框架,然后在访问该节点后弹出
既然我们同意在任何时刻都不超过d
堆栈帧,那么下一步就是确定单个堆栈帧的大小。如果我们确信这是O(1)
,也就是说,输入的大小对堆栈帧大小没有影响,那么我们的总体复杂性是O(d)
这个问题的答案是,尽管Java是按值传递的,“值”不是内存中列表数据结构的副本。相反,它只是对数据结构的引用。引用的大小是恒定的,因此每个帧的开销是固定的
.-------------- heap memory ---------------.
| ... [the actual list data in memory] ... |
`--------------------^---------------------`
|
+----------------+
| |
.---|- frame 0 ----. |
| nestedList sum | |
`------------------` |
|
+----------------+
| |
.---|- frame 1 ----. |
| nestedList sum | |
`------------------` |
|
+----------------+
|
.---|- frame 2 ----.
| nestedList sum |
`------------------`
上图过于简化;这些列表引用并不都指向外部列表,而是外部列表的子列表,因此实际情况是