刷题笔记:二叉树的花式遍历(未完成)

二叉树遍历是笔试面试里常考的问题了。这里做一下总结,以求加深印象。以 LeetCode 相关题目为例。

三序遍历

最常规的遍历是三序遍历:先、中、后。区别在于打印节点的顺序。
先序遍历是“根-左-右”,中序遍历是“左-根-右”,后序遍历是“左-右-根”。
遍历方法有两种:递归和迭代。绝大多数二叉树题目都会涉及递归,递归也是三序遍历最直接的实现,非常简单;迭代则是依靠栈去等效递归,并不很难但需要多理解或者干脆记住。

中序遍历(#94):

// 递归方法

vector<int> inorderTraversal(TreeNode* root){
    vector<int> res;
    recur(root, res);
    return res;
}
void recur(TreeNode* root, vector<int>& res){
    if (root){
        if (root->left)
            recur(root->left, res); // 先左
        res.push_back(root->val); // 再根
        if (root->right)
            recur(root->right, re); // 后右
   }
}
// 迭代方法
vector<int> inorderTraversal(TreeNode* root){
    stack<TreeNode*> s;
    vector<int> res;
    TreeNode* cur = root;
    while (cur || !s.empty()){
        while (cur){
            s.push(cur);
            cur = cur->left; 
        }
        cur = s.top(); s.pop();
        res.push_back(cur->val); 
        cur = cur->right;
 }
    return res;
}

先序遍历(#144):

// 递归
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> res;
        preOrderRecur(root, res);
        return res;
    }
    void preOrderRecur(TreeNode* root, vector<int>& res){
        if (root){
            res.push_back(root->val); // 根
            if (root->left)
                preOrderRecur(root->left, res); // 左
            if (root->right)
                preOrderRecur(root->right, res); // 右
        }
    }

// 迭代

    vector<int> preorderTraversal(TreeNode* root) {
        if (root == nullptr)
            return {};
        stack<TreeNode*> s;
        TreeNode* cur = root;
        vector<int> res;
        s.push(cur);
        while (!s.empty()){
            cur = s.top(); s.pop();
            res.push_back(cur->val);
            if (cur->right) s.push(cur->right);
            if (cur->left)  s.push(cur->left);
        }
        return res;
    }

后序遍历(#145):
后续遍历的迭代实现比较麻烦,要保证左右子树已经依次访问过才能访问根节点。

// 递归
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int> res;
        recur(root, res);
        return res;
    }
    void recur(TreeNode* root, vector<int>& res){
        if (root){
            if (root->left)
                recur(root->left, res);
            if (root->right)
                recur(root->right, res);
            res.push_back(root->val);
        }
    }

有一种取巧的办法,即改写先序遍历的迭代版本,存储一个“根-右-左”的答案,最后将其逆序。

    vector<int> postorderTraversal(TreeNode* root) {
        if (root == nullptr)
            return {};
        vector<int> res;
        TreeNode* cur = root;
        stack<TreeNode*> s;
        s.push(cur);
        while (!s.empty()){
            cur = s.top(); s.pop();
            res.push_back(cur->val);
            if (cur->left) s.push(cur->left);
            if (cur->right) s.push(cur->right);
        }
        reverse(res.begin(), res.end());
        return res;
    }

若不能使用逆序,就要设置一个记录节点,保存上一次访问过的元素。

    vector<int> postorderTraversal(TreeNode* root){
        vector<int> res;
        stack<TreeNode*> s;
        TreeNode* cur = root;
        TreeNode* last = nullptr;
        while (cur || !s.empty()){
            if (cur){
                s.push(cur); cur = cur->left; 
            }
            else {
                TreeNode* topNode = s.top(); // 
                if (topNode -> right && last != topNode -> right)
                    cur = topNode->right;
                else{
                    res.push_back(topNode->val);
                    last = topNode; s.pop();
                }
            }
        }
        return res;
    }

深度遍历(#102, #107)

两个题目要求相反。
102 要求按树的样子从上(根)到下(叶)逐层打印出来。可以借助队列 queue 完成。

    vector<vector<int>> levelOrder(TreeNode* root) {
        if (root == nullptr)
            return {};
        vector<int> row;
        vector<vector<int>> res;
        queue<TreeNode*> q;
        q.push(root);
        int count = q.size();
        while (!q.empty()){
            if (q.front()->left) q.push(q.front()->left);
            if (q.front()->right) q.push(q.front()->right);
            row.push_back(q.front()->val);
            q.pop();
            count--;
            if (count == 0){
                res.push_back(row);
                row.clear();
                count = q.size();
            }
        }
        return res;
    }

所以 107 也就很简单了。增加一行 reverse 将结果逆序即可。
也有递归方法:

    vector<vector<int>> levelOrderBottom(TreeNode* root){
        vector<vector<int>> res;
        levelRecur(root, res, 0);
        reverse(res.begin(), res.end());
        return res;
    }
    void levelRecur(TreeNode* root, vector<vector<int>>& res, int level){
        if (root == nullptr) return;
        if (res.empty() || level > res.size() - 1)
            res.push_back({});
        res[level].push_back(root->val);
        levelRecur(root->left, res, level+1);
        levelRecur(root->right, res, level+1);
    }

最小(大)深度(#104, #111)

先看最小深度。注意最小深度指根节点到叶子的最短距离,所以根节点有一个叶子节点的情况下不能算 path=1 ,比如输入[1, 2] 输出应为2;输入为空的时候返回 0;输入为一个根节点的时候返回1
递归做法 :

    int minDepth(TreeNode* root) {
        if (!root)
            return 0;
        if (!root->left) return 1+minDepth(root->right);
        if (!root->right) return 1+minDepth(root->left);
        return min(minDepth(root->left), minDepth(root->right))+1;
    }

迭代做法1:

    int minDepth(TreeNode* root) {
        if (!root) return 0;
        queue<TreeNode*> q;
        q.push(root);
        int depth = 0;
        while (!q.empty()){
            depth ++;
            int k = q.size();
            for (int j = 0; j < k; j++){
                TreeNode* rt = q.front();
                if (rt->left) q.push(rt->left);
                if (rt->right) q.push(rt->right);
                q.pop();
                if (rt->left == nullptr && rt->right == nullptr) return depth ;
            }
        }
        return -1;

注意到这个法子跟上面逐层遍历二叉树其实思路完全一样,都是 BFS。所以两份代码稍微改改就能互换了:

// 方法2
    int minDepth(TreeNode* root) {
        int depth = 1;
        if (!root) return 0;
        queue<TreeNode*> q;
        q.push(root);
        int k = q.size();
        while (!q.empty()){
            if (q.front()->left) q.push(q.front()->left);
            if (q.front()->right) q.push(q.front()->right);
            if (q.front()->left == nullptr && q.front()->right == nullptr) return depth;
            q.pop();
            k--;
            if (k == 0){
                k = q.size();
                depth++;
            }
        }
        return -1;
    }

但有趣的是 OJ 跑过几次,深度遍历题用方法2速度快,最小深度用方法1速度快,有点微妙。

最大深度比较简单。递归轻松解:

    int maxDepth(TreeNode* root) {
        if (root==nullptr)
            return 0;
        return max(maxDepth(root->left), maxDepth(root->right)) + 1;
    }

迭代思路跟上文类似:

    int maxDepth(TreeNode* root) {
        if (root==nullptr)
            return 0;
        int depth = 0;
        queue<TreeNode*> q;
        q.push(root);
        while (!q.empty()){
            depth++;
            int k = q.size();
            for (int i = 0; i < k; i++){
                TreeNode* tp = q.front();
                q.pop();
                if (tp->left) q.push(tp->left);
                if (tp->right) q.push(tp->right);
            }

        }
        return depth;
    }

当然另一种写法也是完全没问题的。

两种写法有很细微的差异,注意理清楚,不要搞混。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值