94. 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?

方法1: stack

思路:

inorder:left -> root -> right。我们的遍历从跟节点开始,所以一定会想办法把左边的所有节点都带进来,全部输出之后再输出跟节点,因此需要用到stack。具体操作如下:

  1. 根节点入栈
  2. 如果有左节点,连续入栈,直至null
  3. 开始pop,每次pop时由于此时top的左节点都已经在前面出栈了,那么top作为根结点可以打印。同时top如果有有结点,此时应该入栈,并继续向左追溯。
  4. 。。。
  5. 因此每个大循环里都隐含着两个步骤:1. 追溯当前root的最左节点,2. 开始pop左节点和跟节点,并将root移交给pop出来新节点的右节点。

易错点

  1. 整个大循环不需要初始化,通过内部判空来break
  2. 判断null只发生在唯一推入栈的位置,也就是第一步,那么移交root的时候就不需要判空。
// Tushar
// https://youtu.be/nzmtCFNae9k?list=PLrmLmBdmIlpv_jNDXtJGYTPNQ2L1gdHxu
class Solution1 {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        vector<int> result;
        if (root == NULL) return {};
        stack<TreeNode*> myStack;
        
        // 这里不直接判断while(!myStack.empty),而是在中间取break,可以避免root左子树一上来就是null,导致pop一次就退出的情况
        while (true){
            if (root != NULL){
                myStack.push(root);
                root = root -> left; 
                // 这里一定要continue
                // 或者直接用else {} 打包另一种情况
                // continue;
            }
            else {
            // 这种做法保证null不会被压入stack
            // 所以pop取出来的值是安全的,不会root->val出现null pointer
            // 这很重要!
            if (myStack.empty()) break;
            root = myStack.top();
            myStack.pop();
            result.push_back(root->val);
            root = root->right;
            }
        }
        return result;
    }
};

方法2: recursion

方法3: Morris Traversal

Tushar: https://www.youtube.com/watch?v=wGXB9OWhPTg
Annie’s Kim: http://www.cnblogs.com/AnnieKim/archive/2013/06/15/morristraversal.html
94. 官方题解:https://leetcode.com/problems/binary-tree-inorder-traversal/solution/
思路:

为什么traversal一般都会用到stack?因为我们需要某种方法在向下走到叶节点之后还能继续traverse。Morris Traversal 就利用叶节点的null来暂时保存这个信息,留下出口。主要的思路是利用一种叫做Threaded Binary Tree 的结构。Wiki的定义:A binary tree is threaded by making all right child pointers that would normally be null point to the inorder successor of the node (if it exists), and all left child pointers that would normally be null point to the inorder predecessor of the node. 也就是说,对于叶节点,我们通过连接其predecessor 和successor到左/右节点,来保留一个返回祖先的路径。具体算法如下:

Step 1: Initialize current as root

Step 2: While current is not NULL,

If current does not have left child
a. Add current’s value
b. Go to the right, i.e., current = current.right

Else
a. In current’s left subtree, make current the right child of the rightmost node
b. Go to this left child, i.e., current = current.left

易错点

  1. 两种情况下可以标记visited: a.已经没有做左孩子,b.左孩子的右节点已经指向了自己,这是说明左边已经全部遍历过了。
  2. 寻找predecessor的内循环条件:a. 没有更右的节点了,更右的节点已经被连回自己了。
  3. 过河后拆桥

Complexity

Time complexity: O(n)
Space complexity: O(1)

class Solution {
public:
    vector<int> inorderTraversal(TreeNode* root) {
        if (!root) return {};
        vector<int> result;
        TreeNode * cur, * prev;
        cur = root;
        
        while (cur) {
            // left is null then visit cur and go to right
            if (!cur -> left) {
                result.push_back(cur -> val);
                cur = cur -> right;
            }
            else {
                // find the predecessor;
                prev = cur -> left;
                while (prev -> right && prev -> right != cur) {
                    prev = prev -> right;
                }
                // if right node is null then establish link from predecessor to current
                // then go to left 
                if (! prev -> right) {
                    prev -> right = cur;
                    cur = cur -> left;
                }
                // left is already visited. Go right after visiting current
                else {
                    result.push_back(cur -> val);
                    prev -> right = nullptr;
                    cur = cur -> right;
                }
            }
        }
        return result;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值