在电话采访中,我被要求使用迭代器和堆栈(非递归)实现二进制搜索树的有序遍历。我不允许使用父指针。
这是我收到的入门代码。
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}};
class BTIterator
{
public:
BTIterator(TreeNode *root){
};
TreeNode* next() {
}
bool hasNext() {
}
};
测试功能:
void TestFunc(TreeNode *root) {
BTIterator bti(root);
while(bti->hasNext()) {
cout << bti->next()->val << " ";
}}
我被特别要求在上述代码中实现BTIterator
,next
,hasNext
。
所以我做到了。 后续问题是时间和空间的复杂性是什么。 所以我回答时间是O(N),空间是O(N)。 但是,面试官说:“您可以进一步降低空间复杂度, O(log N)”。我问他怎么做,他说“我们只需要存储父母”(我可能听错了他的话。他的口音很浓。)我的实现是存储每个留有孩子的节点我只是认为他的答案是理所当然的。
但是,我认为在面试之后,即使我们只需要存储父母(而不是叶子节点),它仍然是O(N)。这是precisley O(N / 2),但仍然是O(N)。我相信任何留下孩子的节点都应存储在堆栈中。怎么不?
唯一可以实现空间O(logN)的时间是二叉树只有一个分支不断下降的情况(不是具有全叶的平衡树)。
我在这里想念什么?如果有人能解释如何将使用迭代器的空间复杂度进一步降低到O(log N),我将不胜感激!
答案 0 :(得分:3)
我想我理解你的困惑。
考虑一下这棵树(只是我们有一个具体的例子可以参考):
A
/ \
B C
/ \ / \
D E F G
在遍历该树的整个过程中,您将需要存储每个具有左子节点的节点-三个节点A
,B
和C
。通常,对于任何树,您都需要在迭代过程中最多存储 O ( n )个节点。这似乎就是为什么您说 O ( n )。
但是您不必一次所有保留所有节点 。迭代到E
后,您将不再需要保留节点B
。在迭代的任何给定点,您只需要保留 current 节点的 later 祖先,该节点最多是两个节点,即A
和{ {1}}(当前节点为B
时)。通常,对于任何一棵树,您都不需要同时存储超过 O ( h )个节点,其中 h 是树的高度。假设有一棵平衡的树(就像您的面试官清楚的那样),这意味着 O (log n )。
因此,您不需要 O ( n )多余的空间,因为随着时间的推移您可以重复使用空间。那就是使用堆栈的目的:您可以从顶部弹出一个元素,然后将新元素推入其位置。
答案 1 :(得分:0)
如果二分搜索树(BST)不平衡并且我们必须使用堆栈方法,则 public static ArrayList<String> convertCSVtoArrayList(String pathCSV) {
ArrayList<String> result = new ArrayList<>();
if (pathCSV != null) {
String[] splitData = pathCSV.split("\\s+");
for (int i = 0; i < splitData.length; i++) {
if (splitData[i].length() != 0) {
result.add(splitData[i].trim());
}
}
}
return result;
}
是空间复杂度,其中O(h)
是给定BST的高度。显然,如果遵循堆栈方法,不可能实现更好的空间复杂性。
如果给定的BST是平衡的,或者允许您平衡它,则有可能实现h
的空间复杂性,其中O(logn)
是给定的BST中的节点数。
很显然,如果您不被迫使用堆栈方法,则可以在时间和空间复杂性之间进行权衡。如果允许进行预处理,请使用Morris in-order traversal using threading以获得n
的额外空间和O(n)
的时间复杂度。另外,如果不允许预处理,则可以只存储O(1)
TreeNode。调用current
时,对于平衡的BST,在next()
的时间内,对于不平衡的BST,在O(logn)
的时间内,找到存储的当前TreeNode的最小上限。从O(n)
返回之前更新current
。因此,您可以将时间用于next()
空间复杂度。