Leetcode94. Binary Tree Inorder Traversal
文章目录
Given a binary tree, return the inorder traversal of its nodes’ values.
Example:
Input: [1,null,2,3]
1
\
2
/
3
Output: [1,3,2]
Follow up: Recursive solution is trivial, could you do it iteratively?
解法一 经典递归
中序遍历:左子树——根节点——右子树。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
getAns(root, ans);
return ans;
}
private void getAns(TreeNode node, List<Integer> ans) {
if (node == null) {
return;
}
getAns(node.left, ans);
ans.add(node.val);
getAns(node.right, ans);
}
解法二 基于栈的遍历
思路:思路:每到一个节点 A,因为根的访问在中间,将 A 入栈。然后遍历左子树,接着访问 A,最后遍历右子树。 在访问完 A 后,A 就可以出栈了。因为 A 和其左子树都已经访问完成。
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (cur != null || !stack.isEmpty()) {
//节点不为空一直压栈
while (cur != null) {
stack.push(cur);
cur = cur.left; //考虑左子树
}
//节点为空,就出栈
cur = stack.pop();
//当前值加入
ans.add(cur.val);
//考虑右子树
cur = cur.right;
}
return ans;
}
...
1 (1)栈为[...,1,2,4], 此时cur为空,停止压栈并开始出栈,cur = node4, cur = node4.right = null
/ (2)栈为[...,1,2],cur为空,继续出栈,cur = node2,cur = node2.right = node5
2 (3)栈为[...1],cur = node5非空,所以node5先压栈再出栈,cur = node5.right = null
/ \ (4)栈为[...1],cur为空,则出栈node1,再分析node1右子树。
4 5
1
/ \
2 3
/ \ /
4 5 6
push push push pop pop push pop pop
| | | | |_4_| | | | | | | | | | |
| | |_2_| |_2_| |_2_| | | |_5_| | | | |
|_1_| |_1_| |_1_| |_1_| |_1_| |_1_| |_1_| | |
ans add 4 add 2 add 5 add 1
[] [4] [4 2] [4 2 5] [4 2 5 1]
push push pop pop
| | | | | | | |
| | |_6_| | | | |
|_3_| |_3_| |_3_| | |
add 6 add 3
[4 2 5 1 6] [4 2 5 1 6 3]
解法三 Morris Traversal(利用线索二叉树)
解法一和解法二本质上是一致的,都需要 O(h)
的空间来保存上一层的信息。由于中序遍历是左子树——根节点——右子树,所以我们可以把当前根节点存起来,然后遍历左子树,左子树遍历完以后回到当前根节点就可以了,这样就不用存储上一层的信息。
① 当前根节点有左子树:左子树最后遍历的节点一定是一个叶子节点,它的左右孩子都是 null
,我们把它右孩子指向当前根节点存起来,这样的话我们就不需要额外空间了。这样做,遍历完当前左子树,就可以回到根节点了。
②当前根节点左子树为空,那么我们只需要保存根节点的值,然后考虑右子树即可。
思路
记当前遍历的节点为 cur
。
-
cur.left
为null
,保存cur
的值,更新cur = cur.right
-
cur.left
不为null
,找到cur.left
这颗子树最右边的节点记做last
-
last.right
为null
,那么将last.right = cur
,更新cur = cur.left
-
last.right
不为null
,说明之前已经访问过,第二次来到这里,表明当前子树遍历完成,保存cur
的值,更新cur = cur.right
-
public List<Integer> inorderTraversal3(TreeNode root) {
List<Integer> ans = new ArrayList<>();
TreeNode cur = root;
while (cur != null) {
//情况 1
if (cur.left == null) {
ans.add(cur.val);
cur = cur.right;
} else {
//找左子树最右边的节点
TreeNode pre = cur.left;
while (pre.right != null && pre.right != cur) {
pre = pre.right;
}
//情况 2.1
if (pre.right == null) {
pre.right = cur;
cur = cur.left;
}
//情况 2.2
if (pre.right == cur) {
pre.right = null; //这里可以恢复为 null
ans.add(cur.val);
cur = cur.right;
}
}
}
return ans;
}
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( 1 ) O(1) O(1)
图解莫里斯方法
①cur->left
不为null
,将cur
的左子树最右边的节点的右孩子指向cur
②更新 cur = cur.left
③如上图,当前cur.left
不为 null
,将cur
的左子树最右边的节点的右孩子指向cur
④更新 cur = cur.left
⑤cur.left
为 null
,保存 cur
的值,更新 cur = cur.right
。
⑥cur.left
不为 null
,cur
的左子树最右边的节点的右孩子已经指向 cur
,保存 cur
的值,更新 cur = cur.right
。
⑦cur.left
为 null
,保存 cur
的值,更新 cur = cur.right
⑧cur.left
不为 null
,cur
的左子树最右边的节点的右孩子已经指向 cur
,保存 cur
的值,更新 cur = cur.right
。
⑨cur.left
为 null
,保存 cur
的值,更新 cur = cur.right
。
⑩cur
指向 null
,结束遍历。
四、颜色标记法
其核心思想如下:
- 使用颜色标记节点的状态,新节点为白色,已访问的节点为灰色。
- 如果遇到的节点为白色,则将其标记为灰色,然后将其右子节点、自身、左子节点依次入栈。
- 如果遇到的节点为灰色,则将节点的值输出。
优点:对于前序、中序、后序遍历,能够写出完全一致的代码。
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
WHITE, GRAY = 0, 1
res = []
stack = [(WHITE, root)]
while stack:
color, node = stack.pop()
if node is None: continue
if color == WHITE:
stack.append((WHITE, node.right))
stack.append((GRAY, node))
stack.append((WHITE, node.left))
else:
res.append(node.val)
return res
再优化:white对应TreeNode数据类型,gray对应int数据类型,所以不需要额外的颜色标记:
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
stack,rst = [root],[]
while stack:
i = stack.pop()
if isinstance(i,TreeNode):
stack.extend([i.right,i.val,i.left])
elif isinstance(i,int):
rst.append(i)
return rst
五、C++代码
Iterative solution using stack
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> nodes;
stack<TreeNode*> todo;
while (root || !todo.empty()) {
while (root) {
todo.push(root);
root = root -> left;
}
root = todo.top();
todo.pop();
nodes.push_back(root -> val);
root = root -> right;
}
return nodes;
}
};
Recursive solution
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> nodes;
inorder(root, nodes);
return nodes;
}
private:
void inorder(TreeNode* root, vector<int>& nodes) {
if (!root) {
return;
}
inorder(root -> left, nodes);
nodes.push_back(root -> val);
inorder(root -> right, nodes);
}
};
Morris traversal
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> nodes;
while (root) {
if (root -> left) {
TreeNode* pre = root -> left;
while (pre -> right && pre -> right != root) {
pre = pre -> right;
}
if (!pre -> right) {
pre -> right = root;
root = root -> left;
} else {
pre -> right = NULL;
nodes.push_back(root -> val);
root = root -> right;
}
} else {
nodes.push_back(root -> val);
root = root -> right;
}
}
return nodes;
}
};