这里是对力扣题目力扣官方解答中方法二的解释,我认为官方解答可能不太好理解,在这里做一个笔记,供之后回忆时查看。
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
if (!preorder.size()) {
return nullptr;
}
TreeNode* root = new TreeNode(preorder[0]);
stack<TreeNode*> stk;
stk.push(root);
int inorderIndex = 0;
for (int i = 1; i < preorder.size(); ++i) {
int preorderVal = preorder[i];
TreeNode* node = stk.top();
if (node->val != inorder[inorderIndex]) {
node->left = new TreeNode(preorderVal);
stk.push(node->left);
}
else {
while (!stk.empty() && stk.top()->val == inorder[inorderIndex]) {
node = stk.top();
stk.pop();
++inorderIndex;
}
node->right = new TreeNode(preorderVal);
stk.push(node->right);
}
}
return root;
}
};
作者:LeetCode-Solution
链接:https://leetcode.cn/problems/zhong-jian-er-cha-shu-lcof/solution/mian-shi-ti-07-zhong-jian-er-cha-shu-by-leetcode-s/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
为什么我们需要 首先 得到下面这个结论?
在先序遍历的序列中,如果有前后相邻的两个结点u、v,那么有且仅有下面三种关系:
1. u有左子,那么v是u的左子
2. u没有左子,有右子,那么v是u的右子
3. u没有左子,没有右子,那么u是一个叶子结点,所以访问u结束后意味着u所在的左子树访问结束,v是这个左子树的祖先的右子综上,有两种关系:
1. v是u的左子
2. v是u或者u的某个祖先的右子
这肯定是因为这是方法二的基础和关键理论,简称理论基础。
那么,一句话概括,方法二是怎么从先序、中序序列中构造出二叉树的呢?
简单来说就是由于二叉树中除了根节点外,每个结点都有祖先结点,或者是某个结点的左子,或者是其右子。然后找到这些有父节点的结点的父节点是哪个,建立起来这个关系。
然而,先序顺序中第一个元素就是没有祖先的根节点,所以通过从下标1开始遍历先序顺序,就是遍历那些需要祖先结点的结点,只需要在遍历这些结点的过程中
- 创建结点;
- 找到其祖先结点,也就是让某个结点的左子或者右子指向它
所以我们一开始需要明白先序顺序中相邻的两个结点的关系。
接下来我们解释for循环中的判断:
- 为什么栈顶结点不等于index指向结点时,i (从1开始遍历先序序列的指针)指向的结点是栈顶结点的左子并入栈?
- 为什么栈顶结点等于index指向的结点时,需要弹栈并且右移index,并且 i 指向的结点时最右一个弹出的结点的右子?
简单来说就是,这是stack和index的定义决定的,并且维持这样一个属性:index指向的元素是栈顶元素的的子树中序遍历的第一个元素,这暗含这另一个属性,就是index没有左子。
比如在起始时,根节点入栈并且index指向中序的第一个元素,这是满足我们上面这个属性的。
下面我们解释这个属性何时被破坏以及如何维护它。
如果栈顶元素不等于index指向的元素,那么index指向的元素在栈顶元素的左子中。
这是因为中序遍历的第一个元素是”最左的元素“,也就是没有左子的最高结点。如果根节点没有左子,那么根节点就是中序遍历的第一个结点。反过来说,当根节点不是中序遍历的第一个结点,那么根节点有左子,并且index指向的元素在左子树中。所以更新根节点为当前根节点的左子。这就暗含一个关系,栈顶元素有左子。
并且,根据首先得到的结论,如果一个元素有左子,那么其左子在先序中排在其后并且相邻,这就回答了我们上面提到的第一个为什么。
在上面入栈的过程中,上面提到的属性没有被破坏,直到栈顶元素等于index指向的元素(仍未被破坏),因为index指向的元素仍是栈顶元素的子树中序遍历的第一个元素。
由于index指向的元素是没有左子的,所以此时某个左子树被建立完毕,这个左子树目前来看就是栈顶元素本身作为根节点的左子树,但是我们还可以进一步探寻同时被建立完成的左子树,因为当栈顶元素的祖先没有右子,那么这个祖先为根节点的左子树也被建立完毕。
所以我们需要找到最高的右子没有被访问的结点,这个结点是栈中的某个结点,并且 i 是这个结点的右子。
由于中序顺序的特点,如果uv相邻,那么v是u的祖先或者u的右子树的第一个中序结点,这两种情况是对立事件,意味着如果一个不成立,那么一定是另一种情况。
此时栈顶元素是等于index指向的元素的,再同时右移index和弹栈。如果栈顶元素和index指向的元素相同,那么index此时指向的元素是上一个元素的祖先,并且上一个index指向的元素没有右子(由上面提到的对立事件)。所以被构造完毕的子树又高了一层,如此右移index和弹栈,直到栈被弹空或者两者元素不相等。这时有 i 指向的元素是最后一个弹出的元素的右子。
在弹栈和index右移的过程中,我们上面提到的属性显然被破坏。
根据我们上面的分析,我们找到了被完全建立的左子树,并且找到这个左子树的父节点。现在的index指向这个父节点的右子树中序第一个结点,并且我们把新建立的结点入栈。如此上面的属性重新被维护。
所以下一次进入循环的时候,对if的第一个条件内的操作仍然成立。