144. Binary Tree Preorder Traversal**

144. Binary Tree Preorder Traversal**

https://leetcode.com/problems/binary-tree-preorder-traversal/

题目描述

Given a binary tree, return the preorder traversal of its nodes’ values.

Example:

Input: [1,null,2,3]
   1
    \
     2
    /
   3

Output: [1,2,3]

Follow up: Recursive solution is trivial, could you do it iteratively?

C++ 实现 1

前序遍历的递归形式.

class Solution {
private:
    vector<int> res;
    void preorder(TreeNode *root) {
        if (!root) return;
        res.push_back(root->val);
        preorder(root->left);
        preorder(root->right);
    }
public:
    vector<int> preorderTraversal(TreeNode* root) {
        preorder(root);
        return res;
    }
};

C++ 实现 2

使用栈来完成前序遍历, 首先将 root 节点压入栈中, 再将其弹出, 弹出的过程就完成了对根节点的遍历, 之后为了完成 “先遍历左子树, 再遍历右子树”, 考虑到栈的 “先入后出” 的特性, 此时应该先将 root 的右孩子压入栈中, 然后将左孩子压入栈中.

另外一个问题, 为啥会想到栈呢 ? 首先, 常用的数据结构就那么一些, 队列 (用于层序遍历), 栈 … 再考虑到 DFS 方法遍历树时, 递归调用函数的底层实现也是用栈完成的.

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        if (!root) return {};
        vector<int> res;
        stack<TreeNode*> st;
        st.push(root);
        while (!st.empty()) {
            auto root = st.top();
            st.pop();
            res.push_back(root->val);
            if (root->right) st.push(root->right);
            if (root->left) st.push(root->left);
        }
        return res;
    }
};

C++ 实现 3

前序遍历的迭代形式. 前序遍历, 中序遍历(参见94. Binary Tree Inorder Traversal**)和后序遍历(145. Binary Tree Postorder Traversal***)都需要用到栈. 为了方便解析, 我先给出全部代码, 然后再来解析:

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        if (!root) return {};
        vector<int> res;
        stack<TreeNode*> st;
        TreeNode *p = root;
        while (!st.empty() || p) {
            if (p) {
                res.push_back(p->val);
                st.push(p);
                p = p->left;
            } else {
                auto cur = st.top();
                st.pop();
                p = cur->right;
            }
        }
        return res;
    }
};

p 来表示当前访问的节点, 如果把 while 循环中关于栈的代码给抽离, 看看形式:

if (p) {
     res.push_back(p->val);
     p = p->left;
 } else {
     p = cur->right;
 }

如果再去对比中序遍历的代码(参见 94. Binary Tree Inorder Traversal**), 更容易清楚的感受到, 这段代码和递归的形式差不多, 前序遍历先访问当前节点, 再访问左右子树. 如果当前节点不为空, 那么就入栈 (联想递归), 入栈相当于要保存当前函数的状态, 然后安心的去访问左子树. 当左子树访问完, 需要访问右子树, 此时应该从栈中恢复上一层函数的状态. 于是就变成了:

if (p) {
    res.push_back(p->val);
    st.push(p);
    p = p->left;
} else {
    auto cur = st.top();
    st.pop();
    p = cur->right;
}

错误代码!!!

从错误代码中进行学习也是有必要的, 这个 bug 花了我一点时间才找到… 真的一不小心就容易出现偏差.

先看代码:

class Solution {
public:
    vector<int> preorderTraversal(TreeNode* root) {
        if (!root) return {};
        vector<int> res;
        stack<TreeNode*> st;
        TreeNode *p = root;
        while (!st.empty() || p) {
            if (p) {
                res.push_back(p->val);
                st.push(p);
                p = p->left;
            } else {
                auto p = st.top();
                st.pop();
                p = p->right;
            }
        }
        return res;
    }
};

这个实现和 C++ 实现 2 中的代码看起来没有太大区别, 主要不同点如果不仔细看的话, 很难发现出来. 考验一下, 你现在还能看出两段代码的差别吗?
🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣
🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣🤣
揭露答案:

区别在:

// C++ 实现 2 中的代码
auto cur = st.top();
st.pop();
p = cur->right;

// 本段代码
auto p = st.top();
st.pop();
p = p->right;

这会造成 bug, 因为 while 循环中需要利用 p 来判断是否跳出, 这个 p 是 while 外面定义的, 但是本段代码中的

auto p = st.top();

用局部变量覆盖了外面定义的 p… 尴尬, 不仔细看真的很难发现.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值