📝个人主页:@Sherry的成长之路
🏠学习社区:Sherry的成长之路(个人社区)
📖专栏链接:练题
🎯长路漫漫浩浩,万事皆有期待
二叉树的层序遍历
二叉树的层序遍历就是广度优先搜索的一种典型实例,题解思路十分重要,先说广搜的原理,它的原理就是使用队列这个数据结构将每一层需要遍历的数据放入到队列中,然后将他们的下一层放入队列后,取出本层的数据,循环往复,直到队列为空,说明遍历结束
这道题是考察广搜的经典题目
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode*>q;
vector<vector<int>>result;
vector<int>res;
if(root)
q.push(root);
while(!q.empty())
{
int size=q.size();
while(size--)
{// 这里一定要使用固定大小size,不要使用que.size(),因为que.size是不断变化的
TreeNode*node=q.front();q.pop();
res.push_back(node->val);
if(node->left)q.push(node->left);
if(node->right)q.push(node->right);
}
result.push_back(res);
res.clear();
}
return result;
}
};
前面已经说了广搜要创建队列,我们创建好队列了之后 将头节点加入队列,然后创立一个size变量用来保存当前数层中元素的个数,这一点很重要,我们要知道当前数层中有几个数据,才能知道要取出几个数据,接着我们进入循环,在循环中用size来判断多久跳出循环,在循环中我们要做的事情就是将数据加入答案数组中,以及将本层数据节点的全部左右孩子都加入进来(实际上每次加入的是队列头部元素的左右孩子,通过不断循环,最后才都加入进来),最后将本层数组加入最后的数组,不要忘记将本层数组清空再进行下一次的循环。
递归法
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);
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;
}
};
翻转二叉树
翻转二叉树实际上就是沿着对称轴反转,注意不能直接交换节点的数值,而是要交换指针。
这道题刚做的时候不太有思路,不知道从何做起,后来看了题解才明白。
思路为将二叉树的左右孩子节点依次反转,即能完成题目要求
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if(root==nullptr)return root;
invertTree(root->left);
invertTree(root->right);
swap(root->left,root->right);
return root;
}
};
前两步就是一直向下找,直到找到最左侧的节点,然后走第二步递归找到和最左侧相邻的右侧节点,然后进行交换节点,完成节点指针交换,代码十分的简洁,利于理解。这样的代码风格有点类似于二叉树中的后序遍历顺序,实际上前序也是可以的,它只不过是先将根节点的左右孩子节点反转,再向下遍历反转,后序是先反转最下面的节点,区别仅此而已,都可以完成题目要求。
但是值得一提的是,中序并不只是将交换数据的代码插入到中间而已,经过二叉树翻转模拟可知,中序翻转时,先翻转左子树后左右子树会交换,这时我们在处理右子树,实际上是处理刚才刚处理过的左子树,实际上的右子树并没有经过处理,所以要把第二三句代码改为root->left才能够完成对真正右子树的翻转。
对称二叉树
对称二叉树的这道题和上一道题翻转二叉树的思路有着某些相似之处。起初我以为是要将该二叉树翻转之后,判断和之前二叉树是否相等呢,但是实际做的时候遇到了一些问题,比如要做模板的二叉树也就是没改动之前的二叉树怎么存储呢?实际上这种做法浪费了更多的空间。
更好的思路应该是:判断二叉树的外侧对应各节点和内侧的对应各节点是否完全相等,如果相等则说明是对称二叉树,这样的思路并不需要额外开辟空间,实践运用上我想应该和上一种思路差不多,都要递归遍历求解。
class Solution {
public:
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); // 左子树:右、 右子树:左
bool isSame = outside && inside; // 左子树:中、 右子树:中 (逻辑处理)
return isSame;
}
bool isSymmetric(TreeNode* root) {
if (root == NULL) return true;
return compare(root->left, root->right);
}
};
代码虽然没有那么简洁,但是思路十分清晰,先是把我们可能跳出递归的所有可能都列了出来,即左节点为空右节点不为空,左节点不为空但是右节点为空的情况,还有左右节点不为空但是值不对应相等这三种判断完了之后,那就剩下左右都为空或者都不为空且对应值相等,很明显这两种都是合法的,这里针对都不为空且对应值相等我们不做判断的原因是因为前面能跳出的情况我们都做了判断,并且两节点不为空且值相等并不是判断正确的理由,它仅仅是我们当前对应节点正确,它应该是我们能够向下遍历的一个原因,而当全部节点都对应完了,还没跳出,两个指向应该同时指向空,代表了当前内侧或外侧遍历完毕对应相等,所以我们这时候再进行判断true。
总结:
今天我们完成了二叉树的层序遍历、翻转二叉树、对称二叉树三道题目,相关的思想需要多复习回顾。接下来,我们继续进行算法练习。希望我的文章和讲解能对大家的学习提供一些帮助。
当然,本文仍有许多不足之处,欢迎各位小伙伴们随时私信交流、批评指正!我们下期见~