所有涉及到中序遍歷的題都可以使用該模板解決
中序遍歷(inorderTraversal)有遞歸,迭代,莫里斯三種解法
遞歸版
public List<Integer> inorderTraversal(TreeNode root) {
//具體的細節可以和這裏不一樣,但思路一致即可,就是左子樹遞歸->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);
}
時間複雜度:O(n),遍歷每個節點。
空間複雜度:O(h),壓棧消耗,h 是二叉樹的高度。
迭代版
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();
/** do something **/
cur = cur.right;
}
時間複雜度:O(n)。
空間複雜度:O(h),棧消耗,h 是二叉樹的高度。
Morris遍歷
解法一和解法二本質上是一致的,都需要 O(h)的空間來保存上一層的信息。而我們注意到中序遍歷,就是遍歷完左子樹,然後遍歷根節點。如果我們把當前根節點存起來,然後遍歷左子樹,左子樹遍歷完以後回到當前根節點cur就可以了,怎麼做到呢?
我們注意到左子樹cur.left最右邊的節點last的右孩子是null,我們可以將last.right = cur即保留當前根節點即可,這樣我們遍歷完左子樹,就可以回到根節點了。
如果當前根節點的左子樹爲null,那我們就直接遍歷根節點,再考慮右子樹cur = cur.right;
所以總體思想就是:記當前遍歷的節點爲 cur:
- cur.left 爲null,保存cur的值,更新cur爲cur.right;
- cur.left不爲null,記prev = cur.left,找到左子樹的最右邊節點記爲last;
- 如果last.right爲null,將last.right = cur;更新cur = cur.left;
- 如果last.right不爲null,說明之前已經訪問過,第二次來到這裏,表明當前子樹遍歷完成,將last.right = null;保存cur的值,更新cur爲cur.right。
結合圖示:
如上圖,cur 指向根節點。 當前屬於 3 的情況,cur.left 不爲 null,cur 的左子樹最右邊的節點的右孩子爲 null,那麼我們把最右邊的節點的右孩子指向 cur。
接着,更新 cur = cur.left。
如上圖,當前屬於 3 的情況,cur.left 不爲 null,cur 的左子樹最右邊的節點的右孩子爲 null,那麼我們把最右邊的節點的右孩子指向 cur。
更新 cur = cur.left。
如上圖,當前屬於情況 1,cur.left 爲 null,保存 cur 的值,更新 cur = cur.right。
如上圖,當前屬於 4 的情況,cur.left 不爲 null,cur 的左子樹最右邊的節點的右孩子已經指向 cur,保存 cur 的值,更新 cur = cur.right。
如上圖,當前屬於情況 1,cur.left 爲 null,保存 cur 的值,更新 cur = cur.right。
如上圖,當前屬於4的情況,cur.left 不爲 null,cur 的左子樹最右邊的節點的右孩子已經指向 cur,保存 cur 的值,更新 cur = cur.right。
當前屬於情況 1,cur.left 爲 null,保存 cur 的值,更新 cur = cur.right。
cur 指向 null,結束遍歷。
根據這個關係,寫代碼:
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
TreeNode cur = root;
while(cur != null){
if(cur.left == null){
ans.add(cur.val); //do something
cur = cur.right;
}else{
TreeNode prev = cur.left;
while(prev.right != null && prev.right != cur)
prev = prev.right;
//情況3
if(prev.right == null){
prev.right = cur;
cur = cur.left;
}
//情況4,不用擔心前一個if會影響後一個,因爲cur已經改變了
if(prev.right == cur){
prev.right = null;
ans.add(cur.val); //do something
cur = cur.right;
}
}
}
return ans; //依據實際情況返回
時間複雜度:O(n)。每個節點遍歷常數次。
空間複雜度:O(1)。