zhaozhibo

一、二叉树的遍历

1.1 二叉树的层序遍历

vector<vector<int>> levelOrder(TreeNode* root) {
    if(root == nullptr) return {};	//判断根节点是否为空
    queue<TreeNode*> que;	//创建队列
    que.push(root);	//根节点入队
    vector<int> ret;	
    while(!que.empty()){
        auto tmp = que.front();	//记录队列中的第一个节点
        que.pop();	//出队
        ret.push_back(tmp->val);//将该节点的值记录下来
        if(tmp->left)    que.push(tmp->left);	//队列是先进先出,因此先将左节点入队
        if(tmp->right)    que.push(tmp->right);	//右节点入队
    }
    return ret;
}

1.2 前序,中序,后序遍历(递归)

void dfs(TreeNode* root, vector<int>& res){
    if(root==nullptr)   return;
    //res.push_back(root->val);	//前序遍历
    dfs(root->left, res);
    //res.push_back(root->val);	//中序遍历
    dfs(root->right, res);
    //res.push_back(root->val);	//后序遍历
}
vector<int> preorderTraversal(TreeNode* root) {
    vector<int> res;
    dfs(root, res);
    return res;
}

1.3 前序,中序,后序遍历(迭代)

//前序遍历
vector<int> preorderTraversal(TreeNode* root) {
    vector<int> res;
    if(root == nullptr) return {};
    stack<TreeNode*>stk;
    stk.push(root);
    while(!stk.empty()){
        TreeNode* tmp = stk.top();
        stk.pop();
        res.push_back(tmp->val);
        if(tmp->right)  stk.push(tmp->right);
        if(tmp->left)   stk.push(tmp->left);
    }
    return res;
}

//中序遍历,三个循环
vector<int> inorderTraversal(TreeNode* root) {
    vector<int>res;
    if(root == nullptr) return res;
    stack<TreeNode*> stk;
    stk.push(root);	//根节点入栈
    while(!stk.empty()){
        //到达了二叉树的左下角
        while(stk.top()->left != nullptr)   stk.push(stk.top()->left);
        
        while(!stk.empty()){
            TreeNode* tmp = stk.top();	stk.pop();	//遍历左下角的节点
            res.push_back(tmp->val);
            if(tmp->right){	//判断该节点是否存在右子树,若存在,将右子树节点入栈,重新找到该右子树的左下角继续重复上述过程
                stk.push(tmp->right);
                break;
            }
        }    
    }
    return res;
}
//后序遍历,需要记录上一次出栈的节点
vector<int> postorderTraversal(TreeNode* root) {
    vector<int> res;
    if(root == nullptr) return res;
    stack<TreeNode*> stk;
    stk.push(root);
    TreeNode* lastnode = nullptr;
    while(!stk.empty()){
        //找到左下角的节点
        while(stk.top()->left != nullptr)   stk.push(stk.top()->left);
        while(!stk.empty()){
            //判断上一次出栈节点是否当前节点的右节点,或者当前节点的右节点是否为空,满足任一条件,将当前节点输出,
            if(lastnode == stk.top()->right || stk.top()->right == nullptr){
                TreeNode* node = stk.top(); stk.pop();
                res.push_back(node->val);
                lastnode = node;
            }
            //查找该节点是否存在右子树
            else if(stk.top()->right != nullptr){
                stk.push(stk.top()->right);
                break;
            }
        }
    }
    return res;
}

二、二叉树的其他题目

LeetCode 105: 从前序遍历与中序遍历序列构造二叉树
输入:preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出:[3,9,20,null,null,15,7]
题目分析:
    前序遍历中第一个节点就是根节点,第二个节点就是左子树的根节点......
    中序遍历中根节点左边就是左子树
因此,我们通过前序遍历来找到节点,通过在中序遍历中的位置来判断是左子树还是右子树根节点,如下图所示:

class Solution {
private:
    unordered_map<int, int> index;

public:
    TreeNode* myBuildTree(const vector<int>& preorder, const vector<int>& inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) {
        if (preorder_left > preorder_right) {
            return nullptr;
        }
        // 前序遍历中的第一个节点就是根节点
        int preorder_root = preorder_left;
        // 在中序遍历中定位根节点
        int inorder_root = index[preorder[preorder_root]];
        
        // 先把根节点建立出来
        TreeNode* root = new TreeNode(preorder[preorder_root]);
        // 得到左子树中的节点数目
        int size_left_subtree = inorder_root - inorder_left;
        // 递归地构造左子树,并连接到根节点
        // 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
        root->left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
        // 递归地构造右子树,并连接到根节点
        // 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
        root->right = myBuildTree(preorder, inorder, preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right);
        return root;
    }
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int n = preorder.size();
        // 构造哈希映射,帮助我们快速定位根节点
        for (int i = 0; i < n; ++i) {
            index[inorder[i]] = i;
        }
        return myBuildTree(preorder, inorder, 0, n - 1, 0, n - 1);
    }
};
LeetCode 105: 路径总和
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和 targetSum 。
输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true
题目分析:
    每个节点不仅有它的 val 值,同时还有一些二叉树的深度,节点数值的和等隐含的属性。在求路径和时,当节点遍历时,将节点与路径和一起作为递归的参数,通过深度优先策略可以判断是否有等于targetSum的路径和。
//递归法
bool hasPathSum(TreeNode* root, int targetSum) { 
    if (root == nullptr) {
        return false;
    }
    if(!root->left && !root->right){
        if(root->val == targetSum)  return true;
        else
            return false;
    }
    bool f1 = hasPathSum(root->left, targetSum - root->val);	//沿着左子树去找
    bool f2 = hasPathSum(root->right, targetSum - root->val);	//沿着右子树去找
    return f1 || f2;
}
//迭代法
 bool hasPathSum(TreeNode* root, int targetSum) {
     if(root == nullptr) return false;
     stack<pair<TreeNode*, int>> stk;	//将下一个节点与剩余的路径和同时存放到栈内
     stk.push(pair<TreeNode*, int>(root, root->val));
     while(!stk.empty()){
         pair<TreeNode*, int> cur = stk.top();   stk.pop();	//取出栈顶的元素,first 为节点,second 为该节点的 val
         TreeNode* node = cur.first;
         int path_value = cur.second;

         if(node->left == nullptr && node->right == nullptr && path_value==targetSum){
             return true;
         }
		// 深度优先搜索,其实就是二叉树的前序遍历,将路径上的节点和剩余路径和一并存入,如果存在
         // node->left == nullptr && node->right == nullptr && path_value==targetSum, 那么返回 true
         if(node->right != nullptr)
             stk.push(pair<TreeNode*, int>(node->right, path_value+node->right->val));
         if(node->left != nullptr)
             stk.push(pair<TreeNode*, int>(node->left, path_value+node->left->val));
     }
     return false;
    }
LeetCode 113: 路径总和II
给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:[[5,4,11,2],[5,8,4,5]]
题目分析:
    这道题跟上题差不多,只不过需要额外定义两个数组,res 和 path。其中 path 记录 dfs 遍历到的节点,当满足条件时就把 path 存入 res, 代替 “return true” 这一步。如果不满足条件,在 dfs 结束时记得弹出 path 的顶端 val, 回退到上一个节点。
//递归法
void dfs(TreeNode* root, int targetSum, vector<vector<int>>& res, vector<int>& path){
    if (root == nullptr) 
        return;
    path.push_back(root->val);
    if(!root->left && !root->right && root->val==targetSum){
        res.push_back(path);
    }
    if(root->left)  dfs(root->left, targetSum - root->val, res, path);
    if(root->right)  dfs(root->right, targetSum - root->val, res, path);
    path.pop_back(); //回溯,返回到上一个节点,撤销处理结果
}
vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
    vector<vector<int>> res;
    vector<int> path;
    dfs(root, targetSum, res, path);
    return res;
}
LeetCode 199: 二叉树的右视图
给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
输入: [1,2,3,null,5,null,4]
输出: [1,3,4]
题目分析:
    这道题采用广度优先的策略会比较好理解,可以参考 `LeetCode 107: 二叉树的层序遍历II`,在层序遍历中,每一个层的最后一个节点就是所要找到的值。如果采用深度优先算法来理解,首先从根节点先访问右子树来确保右边的节点首先出现,然后使用一个 depth 变量来判断该节点是否被挡住。
vector<int> rightSideView(TreeNode* root) {
    //广度优先策略
    if(root == nullptr) return {};
    queue<TreeNode*> que;	//定义层序遍历的队列
    vector<int> res;
    que.push(root);
    while(!que.empty()){
        int cur_size = que.size();	//记录该层的节点个数
        TreeNode* node = nullptr;
        //随着遍历,不断将下一层的节点入队,当遍历完该层的节点时,node所代表的就是该层的最右边节点
        for(int i=0; i<cur_size; i++){	
            node = que.front(); que.pop();
            if(node->left)  que.push(node->left);
            if(node->right)  que.push(node->right);
        }
        res.push_back(node->val);
    }     
    return res;
}   
//深度优先策略
void dfs(TreeNode* root, int depth, vector<int>& res){
    if(root == nullptr) return;
    if(depth == res.size()){	//通过判断 depth 是否与 res 的长度相等,来判断该节点是否被挡住
        res.push_back(root->val);
    }
    dfs(root->right, depth+1, res);
    dfs(root->left, depth+1, res);
}
vector<int> rightSideView(TreeNode* root) {
    //深度优先策略
    vector<int> res;
    dfs(root, 0, res);	//假设根节点的深度为 0
    return res;
}
LeetCode 113: 二叉树的最小深度
给定一个二叉树,找出其最小深度。最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
输入:root = [3,9,20,null,null,15,7]
输出:2
题目分析:
    本题同样可以采取 DFS 和 BFS 的策略。
    使用 BFS 进行层序遍历,当碰到第一个节点 node 且 node->left 与 node->right 均为 nullptr 时,该节点的深度就是二叉树的最小深度。
    使用 DFS 进行前序遍历,必须要记录每一条路径的深度,然后最小的那个就是结果。
//广度优先策略
int minDepth(TreeNode* root) {
    int res=0;
    if(root == nullptr) return res;
    queue<TreeNode*> que;
    que.push(root);
    while(!que.empty()){
        int cur_size = que.size();	//记录下该层的节点个数
        res++;	//没经过一层,深度加 1
        for(int i=0; i<cur_size; i++){
            TreeNode* node = que.front(); que.pop();
            //如果该层中有某个节点的左右子节点都为空,那么提前返回结果
            if(node->left == nullptr && node->right == nullptr)
                return res;    
            if(node->left)  que.push(node->left);
            if(node->right) que.push(node->right);
        }
    }
    return res;    
}
//深度优先策略
int minDepth(TreeNode *root) {
    if (root == nullptr) {
        return 0;
    }

    if (root->left == nullptr && root->right == nullptr) {
        return 1;
    }

    int min_depth = INT_MAX;
    if (root->left != nullptr) {
        min_depth = min(minDepth(root->left), min_depth);
    }
    if (root->right != nullptr) {
        min_depth = min(minDepth(root->right), min_depth);
    }

    return min_depth + 1;
}

相关文章: