代码随想录day14-二叉树(2)

代码随想录day14-二叉树(2)

昨天我们重点讲了二叉树的深度优先遍历,今天我们的重点是二叉树的广度优先遍历

1、LeetCode 102 二叉树的层序遍历

题目分析:
对于二叉树的层序遍历,使用最多的就是迭代法,递归法反而比较麻烦。对于迭代法而言,层序遍历的题目有一套模板,掌握了模板可以解决许多关于层序遍历的题目。与深度优先遍历不同,广度优先遍历二叉树使用的是队列queue

题目解答: 使用迭代法(注意记住解题模板)

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        if (root == nullptr) return {};

        vector<vector<int>> ans;
        queue<TreeNode*> que;
        que.push(root);
        while (!que.empty()) {
            int size = que.size();  // 这里统计出每一层的结点的个数,待会儿依次遍历
            vector<int> temp;
            for (int i = 0; i < size; i++) {   // 这里使用固定的size而非que.size(),是因为que.size()在不断变化
                TreeNode* node = que.front();
                que.pop();
                temp.emplace_back(node->val);  // 得到当前结点的值
                if (node->left) que.push(node->left);
                if (node->right) que.push(node->right);
            }
            // 一层遍历结束
            ans.emplace_back(temp);
        }
        return ans;
    }
};

上述的迭代法解决层序遍历的问题可以看作是一个模板。在上面的代码中,我们没有让空结点入队列,如果让空结点也入队列的话,代码要稍作修改。

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        if (root == nullptr) return {};

        vector<vector<int>> ans;
        queue<TreeNode*> que;
        que.push(root);
        while (!que.empty()) {
            int size = que.size();  // 这里统计出每一层的结点的个数,待会儿依次遍历
            vector<int> temp;
            for (int i = 0; i < size; i++) {   // 这里使用固定的size而非que.size(),是因为que.size()在不断变化
                TreeNode* node = que.front();
                que.pop();
                if (node) temp.emplace_back(node->val);  // 如果当前结点不为空,得到当前结点的值
                else continue;  // 否则的话就不能继续下去了,继续干这一层的其他结点
                que.push(node->left);  // 空结点也可以进入队列
                que.push(node->right);
            }
            // 一层遍历结束
            if (!temp.empty()) ans.emplace_back(temp);  // 注意这里需要判断
        }
        return ans;
    }
};

关于层序遍历也可以使用递归的方法进行解答,不过在递归的时候,还需要回溯

class Solution {
public:
    void order(TreeNode* cur, vector<vector<int>>& result, int depth)
    {
        if (cur == nullptr) return;
        if (result.size() == depth) result.push_back(vector<int>());
        result[depth].push_back(cur->val);
        order(cur->left, result, depth + 1);  // 注意这里隐藏了回溯的过程,depth进行了回溯
        order(cur->right, result, depth + 1);
    }
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> result;
        int depth = 0;
        order(root, result, depth);
        return result;
    }
};

其中,将回溯的过程展示出来如下所示:

    void order(TreeNode* cur, vector<vector<int>>& result, int depth)
    {
        if (cur == nullptr) return;
        if (result.size() == depth) result.push_back(vector<int>());
        result[depth].push_back(cur->val);
        depth++;
        order(cur->left, result, depth);  // 这里隐藏了回溯的过程,depth进行了回溯
        depth--;  // depth回溯,干完了这一层就depth--
        depth++;
        order(cur->right, result, depth);
        depth--;
    }

通过以上模板,我们可以做很多类似的题目。

2、LeetCode 107 二叉树的层序遍历 II

题目分析:
本题和二叉树层序遍历完全一致,最后进行反转即可。

题目解答:

class Solution {
public:
    vector<vector<int>> levelOrderBottom(TreeNode* root) {
        if (root == nullptr) return {};

        queue<TreeNode*> que;
        vector<vector<int>> ans;
        que.push(root);
        while (!que.empty()) {
            vector<int> temp;
            int size = que.size();
            for (int i = 0; i < size; i++) {
                TreeNode* node = que.front();
                temp.emplace_back(node->val);
                que.pop();
                if (node->left != nullptr) que.push(node->left);
                if (node->right != nullptr) que.push(node->right);
            }
            ans.emplace_back(temp);
        }
        reverse(ans.begin(), ans.end());  // 最后进行一个反转
        return ans;
    }
};

使用递归:

class Solution {
public:
    void order(TreeNode* node, vector<vector<int>>& result, int depth) {
        if (node == nullptr) return;
        if (result.size() == depth) result.emplace_back(vector<int>());  // 如过result.size() == depth的话,那么S说明此时这个刚刚到这个depth,result需要加一个vector存这个depth对应的层,注意depth是从开始的
        result[depth].emplace_back(node->val);  // 中
        order(node->left, result, depth + 1); // 注意这里隐藏了回溯的过程,因为depth+1并没有改变depth的值,等这条语句执行完,depth仍然还是之前的depth
        order(node->right, result, depth + 1);  // 右
    }   
    
    vector<vector<int>> levelOrderBottom(TreeNode* root) {
        int depth = 0;
        vector<vector<int>> ans;
        order(root, ans, depth);
        reverse(ans.begin(), ans.end());  // 进行反转
        return ans;
    }
};

3、LeetCode 199 二叉树的右视图

题目分析:
仍然是可以直接使用层序遍历的题目。

题目解答:

class Solution {
public:
    vector<int> rightSideView(TreeNode* root) {
        if (root == nullptr) return {};

        // 仍然是层序遍历的变式
        queue<TreeNode*> que;
        vector<int> ans;

        que.push(root);
        while (!que.empty()) {
            vector<int> temp;
            int size = que.size();
            for (int i = 0; i < size; i++) {
                TreeNode* node = que.front();
                temp.emplace_back(node->val);
                que.pop();
                if (node->left != nullptr) que.push(node->left);
                if (node->right != nullptr) que.push(node->right);
            }
            ans.emplace_back(temp[temp.size() - 1]);
        }
        return ans;
    }
};

4、LeetCode 637 二叉树的右视图

题目解答:

class Solution {
public:
    vector<double> averageOfLevels(TreeNode* root) {
        // 仍然可以使用层序遍历
        if (root == nullptr) return {};

        queue<TreeNode*> que;
        vector<double> ans;
        que.push(root);
        while (!que.empty()) {
            double result;
            vector<double> temp;
            int size = que.size();
            for (int i = 0; i < size; i++) {
                TreeNode* node = que.front();
                temp.emplace_back(node->val);
                que.pop();
                if (node->left != nullptr) que.push(node->left);
                if (node->right != nullptr) que.push(node->right);
            }
            for (int i = 0; i < temp.size(); i++) {
                result += temp[i];
            }
            ans.emplace_back(result / temp.size());
            result = 0;
        }
        return ans;
    }
};

5、LeetCode 429 N叉树的层序遍历

题目分析:
N叉树的层序遍历和二叉树的层序遍历非常类似,不同的地方就是对于二叉树的每一个结点,其只有可能有两个孩子,但是对于N叉树而言,可能存在多个孩子。在进行每一个结点的遍历的时候,需要遍历其所有的结点。
题目解答:

class Solution {
public:
    vector<vector<int>> levelOrder(Node* root) {
        if (root == nullptr) return {};
        // 与二叉树的层序遍历非常类似,仍然是使用队列
        vector<vector<int>> ans;
        queue<Node*> que;
        que.push(root);
        while (!que.empty()) {
            int size = que.size();  // 这个长度就是表示当前队列右几个元素,待会儿全部弹出来
            vector<int> temp;
            // 下面这个循环的意思是先把这一层的处理完毕
            for (int i = 0; i < size; i++) {
                Node* node = que.front();  // 取得对头的元素
                que.pop();
                // 这里还是判断一下是否为空,按照这个设定,应该是不为空的
                if (node) temp.emplace_back(node->val);
                // 将node的孩子结点(应该是不为空的)依次放入队列
                for (auto& ptr : node->children) {
                    que.push(ptr);  
                }
            }
            ans.emplace_back(temp);
        }
        return ans;
    }
};

6、LeetCode 515 在每个树行中找最大值

题目解答:

class Solution {
public:
    vector<int> largestValues(TreeNode* root) {
        if (root == nullptr) return {};
        // 使用层序遍历来解决问题
        queue<TreeNode*> que;
        que.push(root);
        vector<int> ans;
        while(!que.empty()) {
            vector<int> temp;
            int size = que.size();
            for (int i = 0; i < size; i++) {
                TreeNode* node = que.front();
                que.pop();
                temp.emplace_back(node->val);
                if (node->left) que.push(node->left); // 还是让空指针不要进入队列,否则当指针为空,就不是TreeNode*了
                if (node->right) que.push(node->right);
            }
            // temp装的是每一层的元素,求temp中的最大值
            int max = temp[0];
            for (int i = 1; i < temp.size(); i++) {
                max = max < temp[i] ? temp[i] : max;  // 使用三元运算符来判断
            }
            ans.emplace_back(max);
        }
        return ans;
    }
};

7、LeetCode 116 填充每个节点的下一个右侧节点指针

题目解答:

class Solution {
public:
    Node* connect(Node* root) {
        if (root == nullptr) return nullptr;
        // 始终是在一层进行操作,适合使用层序遍历的方法
        queue<Node*> que;
        que.push(root);
        while (!que.empty()) {
            int size = que.size();
            vector<Node*> result;
            for (int i = 0; i < size; i++) {
                Node* node = que.front();
                result.emplace_back(node);
                que.pop();
                if (node->left) que.push(node->left);
                if (node->right) que.push(node->right);
            }
            // 此时的result是每一层的结点
            int len = result.size();  // 先记录一层的结点个数
            result.emplace_back(nullptr);  // 然后在里面补充一个空结点
            for (int i = 0; i < len; i++) {
                result[i]->next = result[i + 1];
            }
        }
        return root;
    }
};

8、LeetCode 117 填充每个节点的下一个右侧节点指针Ⅱ

题目解答:

class Solution {
public:
    Node* connect(Node* root) {
        if (root == nullptr) return nullptr;
        // 始终是在一层进行操作,适合使用层序遍历的方法
        queue<Node*> que;
        que.push(root);
        while (!que.empty()) {
            int size = que.size();
            vector<Node*> result;
            for (int i = 0; i < size; i++) {
                Node* node = que.front();
                result.emplace_back(node);
                que.pop();
                if (node->left) que.push(node->left);
                if (node->right) que.push(node->right);
            }
            // 此时的result是每一层的结点
            int len = result.size();  // 先记录一层的结点个数
            result.emplace_back(nullptr);  // 然后在里面补充一个空结点
            for (int i = 0; i < len; i++) {
                result[i]->next = result[i + 1];
            }
        }
        return root;
    }
};

以上的题目都是可以使用层序遍历的方法快速解决的题目,所以需要把层序遍历方法的模板理解到位。

9、LeetCode 226 翻转二叉树

题目分析:

本题其实上就是使用前序遍历对每个结点的左右孩子进行反转即可,既可以使用迭代法,也可以使用递归的方法进行。

题目解答:迭代法
class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        if (root == nullptr) return root;
        // 使用前序遍历结合迭代法进行操作
        stack<TreeNode*> stk;
        stk.push(root);
        while (!stk.empty()) {
            TreeNode* node = stk.top();
            stk.pop();
            swap(node->left, node->right);  // swap也可以用做结点的交换
            //  由于栈是先进后出,所以要先将右子树压进栈中,由于上面涉及到了node->left的问题,所以要判断是否为空
            if (node->right) stk.push(node->right);
            if (node->left) stk.push(node->left); 
        }
        return root;
    }
};

递归法:

  • 确定函数的参数以及返回值:
    由于实在root上可以实现原地的变换,所以返回值可以为void,递归函数的参数为TreeNode* node,即:
void invert(TreeNode* node)
  • 确定递归的终止条件:
    由于是要遍历整棵树,所以递归的终止条件是遇到空结点就终止。
if (node == nullptr) return;

在以后的题目中,确定递归的终止条件,往往是感觉最简单,实际上是最难的部分,需要考虑到一些特殊的情况

  • 确定单层的递归逻辑:
    对于递归而言,确定单层的递归逻辑有一个要点,也算是一个总结。只需要关注当前这一层(这一个结点)具体需要干什么事情,不需要关注递归进去之后怎么样,只要你能处理好递归终止的条件,程序就能按照你是所想的方向去执行。
    对于本题而言:我们使用的是前序遍历的方法,所以对当前的结点而言,我们只需要做下面三件事:
swap(node->left, node->right);  //  中
invert(node->left);  // 左
invert(node->right);  // 右

此外就不需要考虑更多的东西了。

所以使用递归的整体解答为:

class Solution {
public:
    void invert(TreeNode* node) {
        if(node == nullptr) return;
        swap(node->left, node->right);  // 中
        invert(node->left);  // 左
        invert(node->right);  // 右
    }

    TreeNode* invertTree(TreeNode* root) {
        invert(root);
        return root;
    }
};

10、LeetCode 589 N叉树的前序遍历

题目分析:

与二叉树的前序遍历类似,N叉树的前序遍历也可以使用迭代法或者递归来实现。

题目解答:

迭代法:

class Solution {
public:
    vector<int> preorder(Node* root) {
        if (root == nullptr) return {};
        stack<Node*> stk;
        stk.push(root);
        vector<int> ans;
        while (!stk.empty()) {
            Node* node = stk.top();
            stk.pop();
            if (node) ans.emplace_back(node->val);
            // 先将右边的孩子入栈,然后入栈左边的孩子
            for (int i = node->children.size() - 1; i >= 0; i--) {
                stk.push(node->children[i]);    
            }
        }
        return ans;
    }
};

递归法:

class Solution {
public:
    void traversal(Node* node, vector<int>& ans) {
        // 递归的终止条件
        if (node == nullptr) return;

        // 单层的处理逻辑
        ans.emplace_back(node->val);  // 中
        for (auto& temp : node->children) {
            traversal(temp, ans);  // 从左至右
        }
    }

    vector<int> preorder(Node* root) {
        // 使用递归法
        vector<int> ans;
        traversal(root, ans);
        return ans;
    }
};

这里的单层的处理逻辑可以跟二叉树的基本一样,只是说二叉树最多只有2个结点,但是N叉树有N个结点。

11、LeetCode 590 N叉树的后序遍历

题目分析:

和N叉树的前序遍历基本是一样的,使用迭代法就是入栈的顺序不一样。前序遍历是右边的结点先入栈,后序遍历则是左边的结点先入栈。然后后序遍历最后还需要反转一下数组
使用递归法只需要换一下顺序即可。

题目解答:
class Solution {
public:
    vector<int> postorder(Node* root) {
        if (root == nullptr) return {};
        // 使用非递归来实现,这里我们让空结点也入栈,也有可能不是空结点
        stack<Node*> stk;
        stk.push(root);
        vector<int> ans;
        while (!stk.empty()) {
            Node* node = stk.top();
            stk.pop();
            // 由于我们不知道具体children是不是也包括了空结点,这里还是判断一下
            if (node) ans.emplace_back(node->val);
            // 由于是后序遍历,我们先让左节点进,那么就最后出,然后对整体的结果进行排序即可
            for (int i = 0; i < node->children.size(); i++) {
                stk.push(node->children[i]);
            }
        }
        reverse(ans.begin(), ans.end());
        return ans;
    }
};

递归法:

class Solution {
public:
    // 使用递归
    void traverse(Node* node, vector<int>& vec) {
        if (node == nullptr) return;
        // 先遍历结点的子树
        for (auto& ptr : node->children) {
            traverse(ptr, vec);
        }
        // 再存值
        vec.emplace_back(node->val);
}

    vector<int> postorder(Node* root) {
        vector<int> ans;
        traverse(root, ans);
        return ans;
    }
};

补充:对于第9题而言,使用前序遍历以及后序遍历都是可以的,但是不能使用真正的中序遍历

class Solution {
public:
    void invert(TreeNode* node) {
        if (node == nullptr) return;

        invert(node->left);  // 左
        swap(node->left, node->right);  // 中
        invert(node->right);  // 右
    }

    TreeNode* invertTree(TreeNode* root) {
        invert(root);
        return root;
    }
};

上面是使用中序遍历进行反转的代码,可以看到,我们最开始反转了左子树,然后交换了左右子树,然后反转了右子树。得到的结果应该是,原来的左子树被反转了一次,然后移到了右子树的位置,然后又被反转了一次(此时又反转回来了),最终的结果只是左右子树的根结点交换了,后序的结点并没有交换
如果将代码改为:

class Solution {
public:
    void invert(TreeNode* node) {
        if (node == nullptr) return;

        invert(node->left);  // 左
        swap(node->left, node->right);  // 中
        invert(node->left);  // 右
    }

    TreeNode* invertTree(TreeNode* root) {
        invert(root);
        return root;
    }
};

其中,两次都反转所谓的左子树,就可以了,但是这已经不是真正意义上的中序遍历了

你可能感兴趣的:(代码随想录刷题,leetcode,算法,数据结构)