Leetcode-94. Binary Tree Inorder Traversal

題目描述(中等難度)

在這裏插入圖片描述
二叉樹的中序遍歷。

解法一 遞歸

學二叉樹的時候,必學的算法。用遞歸寫簡潔明瞭,就不多說了。

package inary_Tree_Inorder_Traversal;

import java.util.ArrayList;
import java.util.List;

class TreeNode{
	int val;
	TreeNode left;
	TreeNode right;
	TreeNode(int val){
		this.val=val;
	}
}

public class inary_Tree_Inorder_Traversal1 {
	
	public static List<Integer> inorderTraversal(TreeNode root){
	
	List<Integer> ans=new ArrayList<>();
	getAns(root,ans);
	return ans;
	}

	private static void getAns(TreeNode node, List<Integer> ans) {
		if(node==null) return ;
		
		getAns(node.left,ans);
		ans.add(node.val);
		getAns(node.right,ans);
	}
	public static void main(String[] args) {
		int n=5;
		TreeNode[] node = new TreeNode[n];//以數組形式生成一棵完全二叉樹
		for(int i = 0; i < n; i++)  node[i] = new TreeNode(i);
		
		for(int i = 0; i < n; i++){
			if(i*2+1 < n) node[i].left = node[i*2+1];
			if(i*2+2 < n) node[i].right = node[i*2+2];
		}
		List<Integer> ans=inorderTraversal(node[0]);
		System.out.println(ans);
}
}

時間複雜度:O(n),遍歷每個節點。

空間複雜度:O(h),壓棧消耗,h 是二叉樹的高度。

官方解法中還提供了兩種解法,這裏總結下。

解法二 棧

利用棧,去模擬遞歸。遞歸壓棧的過程,就是保存現場,就是保存當前的變量,而解法一中當前有用的變量就是 node,所以我們用棧把每次的 node 保存起來即可。

模擬下遞歸的過程,只考慮 node 的壓棧。

//當前節點爲空,出棧
if (node == null) {
    return;
}
//當前節點不爲空
getAns(node.left, ans);  //壓棧
ans.add(node.val); //出棧後添加
getAns(node.right, ans); //壓棧
//左右子樹遍歷完,出棧

看一個具體的例子,想象一下吧。

        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]

結合代碼。

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;
}

時間複雜度:O(n)。

空間複雜度:O(h),棧消耗,h 是二叉樹的高度。

解法三 Morris Traversal

解法一和解法二本質上是一致的,都需要 O(h)的空間來保存上一層的信息。而我們注意到中序遍歷,就是遍歷完左子樹,然後遍歷根節點。如果我們把當前根節點存起來,然後遍歷左子樹,左子樹遍歷完以後回到當前根節點就可以了,怎麼做到呢?

我們知道,左子樹最後遍歷的節點一定是一個葉子節點,它的左右孩子都是 null,我們把它右孩子指向當前根節點存起來,這樣的話我們就不需要額外空間了。這樣做,遍歷完當前左子樹,就可以回到根節點了。

當然如果當前根節點左子樹爲空,那麼我們只需要保存根節點的值,然後考慮右子樹即可。

所以總體思想就是:記當前遍歷的節點爲 cur。

1、cur.left 爲 null,保存 cur 的值,更新 cur = cur.right

2、cur.left 不爲 null,找到 cur.left 這顆子樹最右邊的節點記做 last

2.1 last.right 爲 null,那麼將 last.right = cur,更新 cur = cur.left

2.2 last.right 不爲 null,說明之前已經訪問過,第二次來到這裏,表明當前子樹遍歷完成,保存 cur 的值,更新 cur = cur.right

結合圖示:

如上圖,cur 指向根節點。 當前屬於 2.1 的情況,cur.left 不爲 null,cur 的左子樹最右邊的節點的右孩子爲 null,那麼我們把最右邊的節點的右孩子指向 cur。

接着,更新 cur = cur.left。

如上圖,當前屬於 2.1 的情況,cur.left 不爲 null,cur 的左子樹最右邊的節點的右孩子爲 null,那麼我們把最右邊的節點的右孩子指向 cur。

更新 cur = cur.left。

如上圖,當前屬於情況 1,cur.left 爲 null,保存 cur 的值,更新 cur = cur.right。

如上圖,當前屬於 2.2 的情況,cur.left 不爲 null,cur 的左子樹最右邊的節點的右孩子已經指向 cur,保存 cur 的值,更新 cur = cur.right。

如上圖,當前屬於情況 1,cur.left 爲 null,保存 cur 的值,更新 cur = cur.right。

如上圖,當前屬於 2.2 的情況,cur.left 不爲 null,cur 的左子樹最右邊的節點的右孩子已經指向 cur,保存 cur 的值,更新 cur = cur.right。

cur 指向 null,結束遍歷。

根據這個關係,寫代碼

記當前遍歷的節點爲 cur。

1、cur.left 爲 null,保存 cur 的值,更新 cur = cur.right

2、cur.left 不爲 null,找到 cur.left 這顆子樹最右邊的節點記做 last

2.1 last.right 爲 null,那麼將 last.right = cur,更新 cur = cur.left

2.2 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(1)。

參考文章

1.https://zhuanlan.zhihu.com/p/70887882
2.https://leetcode-cn.com/problems/binary-tree-inorder-traversal/

微信公衆號

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章