我昨天接受了一次采访,其中涉及一个非常基本的树数据结构:
t ::= int | (t * t)
其中tTree是整数(叶)或两个t,表示左右子树。这意味着树仅在叶级别具有值。
示例树可能如下所示:
t
/ \
t t
/ \ / \
1 2 3 4
任务是编写一个函数equal(t, t) => bool
,它需要两个tTrees并确定它们是否相等,非常简单。
我编写了相当标准的代码,结果如下(下面是伪代码):
fun equal(a, b) {
if a == b { // same memory address
return true
}
if !a || !b {
return false
}
// both leaves
if isLeaf(a) && isLeaf(b) {
return a == b
}
// both tTrees
if isTree(a) && isTree(b) {
return equal(a->leftTree, b->leftTree)
&& equal(a->rightTree, b->rightTree)
}
// otherwise
return false
}
当被要求给出时间和空间的复杂性时,我很快回答:
O(n) time
O(1) space
我的采访者声称他们可以创建一个树,这样这个相同的功能将在O(2^n)
指数时间内运行。鉴于上述算法,我没有(现在仍然没有)看到这是可能的。我看到该函数以递归方式调用自身两次,但每次调用的输入大小都减半了吗?因为你只是并行检查各个子树。
对此的任何想法或意见都会非常有帮助。
答案 0 :(得分:1)
目前,你的代码是O(n),而你的面试官也错了。代码在空间使用时不是O(1):在最坏的情况下(当树非常不平衡时)它是O(n),因为你的代码是递归的(而不是尾递归)。 / p>
可能他们要求你写一个测试两棵树是否同构的函数。也就是说,他们期望你编写在比较这两棵树时返回true的代码:
* *
/ \ / \
1 2 2 1
然后他们误解了你的解决方案,假设你写了这样做的天真代码,那就是O(2 ^ n)。
另一种可能性是某些指针可以在同一棵树的左右分支中重复使用,允许具有2 ^ n个节点的树在O(n)空间中表示。那么如果' n'是内存中结构的大小,而不是节点的数量,那么访调员的立场是正确的。这是一棵树:
___ ___ ___ ___ ___
/ \ / \ / \ / \ / \
* * * * * 1
\___/ \___/ \___/ \___/ \___/
根位于左侧,它有32个叶节点(全部为1)。