代码随想录算法训练营Day15|102. 二叉树的层序遍历、226. 翻转二叉树、101. 对称二叉树

代码随想录算法训练营Day15|102. 二叉树的层序遍历、226. 翻转二叉树、101. 对称二叉树

102. 二叉树的层序遍历

102. 二叉树的层序遍历

需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而是用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。

而这种层序遍历方式就是图论中的广度优先遍历,只不过我们应用在二叉树上。

使用队列实现二叉树广度优先遍历,动画如下:

102二叉树的层序遍历

代码如下:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> res;
        queue<TreeNode*> que;
        if (root == NULL) return res;
        que.push(root);
        while (!que.empty()) {
            int size = que.size();
            vector<int> layer;
            while (size--) {
                TreeNode* node = que.front();
                que.pop();
                layer.push_back(node->val);
                // 再把当前层的孩子节点压入队列中
                if (node->left) que.push(node->left);
                if (node->right) que.push(node->right);
            }
            res.push_back(layer);
        }
        return res;
    }
};

226. 翻转二叉树

226. 翻转二叉树

①递归法

二叉树的翻转细化到每个节点就是其左/右孩子节点的翻转,递归法主要就是凑三要素:

  1. 确定递归函数的参数和返回值

    • 参数就是要传入节点的指针,不需要其他参数了,通常此时定下来主要参数,如果在写递归的逻辑中发现还需要其他参数的时候,随时补充。
    • 返回值的话其实也不需要,但是题目中给出的要返回root节点的指针,可以直接使用题目定义好的函数,所以就函数的返回类型为TreeNode*
    TreeNode* invertTree(TreeNode* root)
    
  2. 确定终止条件

    当前节点为空的时候,就返回

    if (root == NULL) return root;
    
  3. 确定单层递归的逻辑

    因为是先前序遍历,所以先进行交换左右孩子节点,然后反转左子树,反转右子树。

    swap(root->left, root->right);
    invertTree(root->left);
    invertTree(root->right);
    

完整代码如下:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        invertLeftRight(root);
        return root;
    }

    void invertLeftRight(TreeNode* node) {
        if (node == NULL) return;
        TreeNode* temp = node->left;
        node->left = node->right;
        node->right = temp;
        invertLeftRight(node->left);
        invertLeftRight(node->right);
    }
};

其实我们甚至不用额外创建另外一个递归函数,直接在原函数上进行修改:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        if (root == NULL) return root;
        swap(root->left, root->right);
        invertTree(root->left);
        invertTree(root->right);
        return root;
    }
};

②迭代法「深度优先」

栈跟递归是可以相互转换的。本质上不管「深度优先」还是「广度优先」,只要遍历到所有的二叉树节点并将其左右孩子节点进行交换即可。需要注意的是压栈是node->right要优先于node->left,这样根据栈先入后出(FILO)的特性,节点出栈时才会满足前序遍历要求的「中左右」的正确顺序。

class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        if (root == NULL) return root;
        stack<TreeNode*> st;
        st.push(root);
        while(!st.empty()) {
            TreeNode* node = st.top();              // 中
            st.pop();
            swap(node->left, node->right);
            if(node->right) st.push(node->right);   // 右
            if(node->left) st.push(node->left);     // 左
        }
        return root;
    }
};

③迭代法「广度优先」

也就是层序遍历,层数遍历也是可以翻转这棵树的,因为层序遍历也可以把每个节点的左右孩子都翻转一遍,代码如下:

class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        queue<TreeNode*> que;
        if (root != NULL) que.push(root);
        while (!que.empty()) {
            int size = que.size();
            for (int i = 0; i < size; i++) {
                TreeNode* node = que.front();
                que.pop();
                swap(node->left, node->right); // 节点处理
                if (node->left) que.push(node->left);
                if (node->right) que.push(node->right);
            }
        }
        return root;
    }
};

101. 对称二叉树

101. 对称二叉树

要求二叉树关于中心轴对称,一开始有考虑使用层序遍历的方式进行考虑,我们「广度优先」去遍历获取二叉树每一层,然后反转获取的vector数组,如果出现反转数组与原数组不匹配的情况则说明不对称,但仅靠元素顺序无法判断同一层节点是否在堆成位置,甚至可能在对称轴的同侧。

 同层元素不在对称轴的情况      同层元素不在对称轴同侧的情况
       1                             1
      / \                           / \
     2   2                         2   2
      \   \                           / \
      3    3                          3  3

①递归法

递归还是首先确定递归三要素:终止条件、递归参数和返回值、单层递归的逻辑

  1. 确定递归函数的参数和返回值

因为我们要比较的是根节点的两个子树是否是相互翻转的,进而判断这个树是不是对称树,所以要比较的是两个树,参数自然也是左子树节点和右子树节点。返回值自然是bool类型。

代码如下:

bool compare(TreeNode* left, TreeNode* right)
  1. 确定终止条件

要比较两个节点数值相不相同,首先要把两个节点为空的情况弄清楚!否则后面比较数值的时候就会操作空指针了。

节点为空的情况有:(注意我们比较的其实不是左孩子和右孩子,所以如下我称之为左节点右节点

  • 左节点为空,右节点不为空,不对称,return false
  • 左不为空,右为空,不对称 return false
  • 左右都为空,对称,返回true

此时已经排除掉了节点为空的情况,那么剩下的就是左右节点不为空:

  • 左右都不为空,比较节点数值,不相同就return false

此时左右节点不为空,且数值也不相同的情况我们也处理了。

代码如下:

if (left == NULL && right != NULL) return false;
else if (left != NULL && right == NULL) return false;
else if (left == NULL && right == NULL) return true;
else if (left->val != right->val) return false; // 注意这里我没有使用else

注意上面最后一种情况,我没有使用else,而是elseif, 因为我们把以上情况都排除之后,剩下的就是 左右节点都不为空,且数值相同的情况。

  1. 确定单层递归的逻辑

此时才进入单层递归的逻辑,单层递归的逻辑就是处理 左右节点都不为空,且数值相同的情况。

  • 比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。
  • 比较内测是否对称,传入左节点的右孩子,右节点的左孩子。
  • 如果左右都对称就返回true ,有一侧不对称就返回false 。

代码如下:

bool outside = compare(left->left, right->right);   // 左子树:左、 右子树:右
bool inside = compare(left->right, right->left);    // 左子树:右、 右子树:左
bool isSame = outside && inside;                    // 左子树:中、 右子树:中(逻辑处理)
return isSame;

如上代码中,我们可以看出使用的遍历方式,左子树左右中,右子树右左中,所以我把这个遍历顺序也称之为“后序遍历”(尽管不是严格的后序遍历)。完整代码如下:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        if (root == NULL) return true;
        return compare(root->left, root->right);
    }

    bool compare(TreeNode* left, TreeNode* right) {
        // 先排除空节点情况
        if (left == NULL && right != NULL) return false;
        else if (left != NULL && right == NULL) return false;
        else if (left == NULL && right == NULL) return true;
        else if (left->val != right->val) return false;

        // 判断内外侧情况
        bool outside = compare(left->left, right->right);
        bool inside = compare(left->right, right->left);
        return (inside && outside);
    }
};
posted @ 2022-12-01 10:24  脱线森林`  阅读(1470)  评论(0编辑  收藏  举报