八、 二叉树
二叉树的种类
在我们解题过程中二叉树有两种主要的形式:满二叉树和完全二叉树。
满二叉树
满二叉树:如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。
如图所示:
这棵二叉树为满二叉树,也可以说深度为k,有2^k-1个节点的二叉树。
完全二叉树
什么是完全二叉树?
完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^h -1 个节点。
大家要自己看完全二叉树的定义,很多同学对完全二叉树其实不是真正的懂了。
我来举一个典型的例子如题:
相信不少同学最后一个二叉树是不是完全二叉树都中招了。
之前我们刚刚讲过优先级队列其实是一个堆,堆就是一棵完全二叉树,同时保证父子节点的顺序关系。
二叉搜索树
前面介绍的树,都没有数值的,而二叉搜索树是有数值的了,二叉搜索树是一个有序树。
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉排序树
下面这两棵树都是搜索树
平衡二叉搜索树
平衡二叉搜索树:又被称为AVL(Adelson-Velsky and Landis)树,且具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
如图:
最后一棵 不是平衡二叉树,因为它的左右两个子树的高度差的绝对值超过了1。
C++中map、set、multimap,multiset的底层实现都是平衡二叉搜索树,所以map、set的增删操作时间时间复杂度是logn,注意我这里没有说unordered_map、unordered_set,unordered_map、unordered_map底层实现是哈希表。
所以大家使用自己熟悉的编程语言写算法,一定要知道常用的容器底层都是如何实现的,最基本的就是map、set等等,否则自己写的代码,自己对其性能分析都分析不清楚!
二叉树的存储方式
二叉树可以链式存储,也可以顺序存储。
那么链式存储方式就用指针, 顺序存储的方式就是用数组。
顾名思义就是顺序存储的元素在内存是连续分布的,而链式存储则是通过指针把分布在散落在各个地址的节点串联一起。
链式存储如图:
链式存储是大家很熟悉的一种方式,那么我们来看看如何顺序存储呢?
其实就是用数组来存储二叉树,顺序存储的方式如图:
用数组来存储二叉树如何遍历的呢?
如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。
但是用链式表示的二叉树,更有利于我们理解,所以一般我们都是用链式存储二叉树。
所以大家要了解,用数组依然可以表示二叉树。
二叉树的遍历方式
关于二叉树的遍历方式,要知道二叉树遍历的基本方式都有哪些。
一些同学用做了很多二叉树的题目了,可能知道前中后序遍历,可能知道层序遍历,但是却没有框架。
我这里把二叉树的几种遍历方式列出来,大家就可以一一串起来了。
二叉树主要有两种遍历方式:
- 深度优先遍历:先往深走,遇到叶子节点再往回走。
- 广度优先遍历:一层一层的去遍历。
这两种遍历是图论中最基本的两种遍历方式,后面在介绍图论的时候 还会介绍到。
那么从深度优先遍历和广度优先遍历进一步拓展,才有如下遍历方式:
- 深度优先遍历
- 前序遍历(递归法,迭代法)
- 中序遍历(递归法,迭代法)
- 后序遍历(递归法,迭代法)
- 广度优先遍历
- 层次遍历(迭代法)
在深度优先遍历中:有三个顺序,前中后序遍历, 有同学总分不清这三个顺序,经常搞混,我这里教大家一个技巧。
这里前中后,其实指的就是中间节点的遍历顺序,只要大家记住 前中后序指的就是中间节点的位置就可以了。
看如下中间节点的顺序,就可以发现,中间节点的顺序就是所谓的遍历方式
- 前序遍历:中左右
- 中序遍历:左中右
- 后序遍历:左右中
大家可以对着如下图,看看自己理解的前后中序有没有问题。
最后再说一说二叉树中深度优先和广度优先遍历实现方式,我们做二叉树相关题目,经常会使用递归的方式来实现深度优先遍历,也就是实现前中后序遍历,使用递归是比较方便的。
之前我们讲栈与队列的时候,就说过栈其实就是递归的一种是实现结构,也就说前中后序遍历的逻辑其实都是可以借助栈使用非递归的方式来实现的。
而广度优先遍历的实现一般使用队列来实现,这也是队列先进先出的特点所决定的,因为需要先进先出的结构,才能一层一层的来遍历二叉树。
这里其实我们又了解了栈与队列的一个应用场景了。
具体的实现我们后面都会讲的,这里大家先要清楚这些理论基础。
二叉树的定义
刚刚我们说过了二叉树有两种存储方式顺序存储,和链式存储,顺序存储就是用数组来存,这个定义没啥可说的,我们来看看链式存储的二叉树节点的定义方式。
C++代码如下:
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
123456
大家会发现二叉树的定义 和链表是差不多的,相对于链表 ,二叉树的节点里多了一个指针, 有两个指针,指向左右孩子.
这里要提醒大家要注意二叉树节点定义的书写方式。
在现场面试的时候 面试官可能要求手写代码,所以数据结构的定义以及简单逻辑的代码一定要锻炼白纸写出来。
因为我们在刷leetcode的时候,节点的定义默认都定义好了,真到面试的时候,需要自己写节点定义的时候,有时候会一脸懵逼!
1. 前序遍历
给你二叉树的根节点 root
,返回它节点值的前序遍历。
/**
* 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<int> preorderTraversal(TreeNode* root) {
if(root == nullptr){
return vector<int>{};
}
vector<int> result;
result.push_back(root->val);
if(root->left !=nullptr){
vector<int> tmp = preorderTraversal(root->left);
result.insert(result.end(),tmp.begin(),tmp.end());
}
if(root->right !=nullptr){
vector<int> tmp = preorderTraversal(root->right);
result.insert(result.end(),tmp.begin(),tmp.end());
}
return result;
}
};
class Solution {
public:
void preorder(TreeNode *root, vector<int> &res) {
if (root == nullptr) {
return;
}
res.push_back(root->val);
preorder(root->left, res);
preorder(root->right, res);
}
vector<int> preorderTraversal(TreeNode *root) {
vector<int> res;
preorder(root, res);
return res;
}
};
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/binary-tree-preorder-traversal/solution/er-cha-shu-de-qian-xu-bian-li-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
采用递归的方法进行前序遍历,前序遍历即 根节点-左子节点-右子节点。本体采用的是vector的insert实现的返回值的拼接。第二种解法为官方递归解法,用了引用来使得所有的方法操作的是同一个数组。
2. 后序遍历
给你一棵二叉树的根节点 root
,返回其节点值的 后序遍历 。
/**
* 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<int> postorderTraversal(TreeNode* root) {
vector<int> result;
postorder(root,result);
return result;
}
void postorder(TreeNode* root,vector<int> & result){
if(root == nullptr)
return;
if(root->left != nullptr)
postorder(root->left,result);
if(root->right != nullptr)
postorder(root->right,result);
result.push_back(root->val);
}
};
/**
* 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<int> postorderTraversal(TreeNode* root) {
if(root == nullptr){
return vector<int>{};
}
vector<int> result;
if(root->left !=nullptr){
vector<int> tmp = postorderTraversal(root->left);
result.insert(result.end(),tmp.begin(),tmp.end());
}
if(root->right !=nullptr){
vector<int> tmp = postorderTraversal(root->right);
result.insert(result.end(),tmp.begin(),tmp.end());
}
result.push_back(root->val);
return result;
}
};
同上题。不用官方的那种好像效率更高(用时)
3. 中序
/**
* 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<int> inorderTraversal(TreeNode* root) {
if(root == nullptr){
return vector<int>{};
}
vector<int> result;
if(root->left !=nullptr){
vector<int> tmp = inorderTraversal(root->left);
result.insert(result.end(),tmp.begin(),tmp.end());
}
result.push_back(root->val);
if(root->right !=nullptr){
vector<int> tmp = inorderTraversal(root->right);
result.insert(result.end(),tmp.begin(),tmp.end());
}
return result;
}
};
4. 迭代法的前中后序遍历
/**
* 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<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> result;
if(root == nullptr)
return result;
st.push(root);
while(!st.empty()){
TreeNode * tmp = st.top();
st.pop();
result.push_back(tmp->val);
if(tmp->right)
st.push(tmp->right);
if(tmp->left)
st.push(tmp->left);
}
return result;
}
};
/**
* 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<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> result;
if(root == nullptr)
return result;
st.push(root);
while(!st.empty()){
TreeNode * tmp = st.top();
st.pop();
result.push_back(tmp->val);
if(tmp->left)
st.push(tmp->left);
if(tmp->right)
st.push(tmp->right);
}
reverse(result.begin(),result.end());
return result;
}
};
/**
* 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<int> inorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
TreeNode * temp = root;
while(temp != nullptr || !st.empty()){
if(temp != nullptr){
st.push(temp);
temp = temp->left;
}else{
temp = st.top();
st.pop();
result.push_back(temp->val);
temp = temp->right;
}
}
return result;
}
};
相当于是用栈实现了递归的做法。
5. 层次遍历
给你二叉树的根节点 root
,返回其节点值的 层序遍历。 (即逐层地,从左到右访问所有节点)。
/**
* 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>> result;
queue<TreeNode *> qu;
if(!root)
return result;
// 加入根节点
qu.push(root);
while(!qu.empty()){
int size = qu.size();
vector<int> temp;
for(int i = 0 ; i < size ; i++){
TreeNode *tree = qu.front();
qu.pop();
temp.push_back(tree->val);
// 入队
if(tree->left){
qu.push(tree->left);
}
if(tree->right){
qu.push(tree->right);
}
}
result.push_back(temp);
}
return result;
}
};
说一下本题的解题思路,本来对于这题我没有太多的思路,然后看到了队列queue数据结构,因此从queue方面下手,每次取出队首的元素,并且把左子节点和右子节点都放进队列,这样就能保证是上层的节点优先遍历到,也就是层次遍历,但是由于本题要求的返回值是需要将每一层的节点放到同一个数组,不同层的节点在不同的数组,而如果按照上面的想法,只能说是层次遍历了该二叉树,但是不能让层与层之间层次分明,所以现在问题进一步转化为了如何找到层与层之间的分界,最初的想法是两个循环,内层循环表征着一层的所有节点,但是一直找不到内层循环的中止条件,最后呢,发现可以在外层循环开始,记录下队列大小,遍历完队列大小次之后结束内层循环。一次外层循环,队列中的节点都是同一层的。
树的层次遍历可以使用广度优先搜索实现,也就相当于是广度优先搜索
目测是广度优先搜索可以用队列实现,深度优先搜索可以用递归实现。
7. 199.二叉树的右视图
给定一个二叉树的 根节点 root
,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
/**
* 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<int> rightSideView(TreeNode* root) {
vector<int> result;
queue<TreeNode*> que;
que.push(root);
if(!root) return result;
while(!que.empty()){
int size = que.size();
for(int i = 0 ; i < size - 1 ; i++){
TreeNode * node = que.front();
que.pop();
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
TreeNode * tree = que.front();
result.push_back(tree->val);
que.pop();
if(tree->left) que.push(tree->left);
if(tree->right) que.push(tree->right);
}
return result;
}
};
class Solution {
List<Integer> res = new ArrayList<>();
public List<Integer> rightSideView(TreeNode root) {
dfs(root, 0); // 从根节点开始访问,根节点深度是0
return res;
}
private void dfs(TreeNode root, int depth) {
if (root == null) {
return;
}
// 先访问 当前节点,再递归地访问 右子树 和 左子树。
if (depth == res.size()) { // 如果当前节点所在深度还没有出现在res里,说明在该深度下当前节点是第一个被访问的节点,因此将当前节点加入res中。
res.add(root.val);
}
depth++;
dfs(root.right, depth);
dfs(root.left, depth);
}
}
广度优先搜索会直接明了一点,只需要保存每一层的最后一个节点,即是最右视图。
第二种深度优先搜索,相当于改变了一下方式,首先是添加了depth变量用来判断是否到达下一层,然后是先访问右子节点,从而达到目的。核心要素是添加了depth的判断,避免同一层在递归的时候重复添加,并且由于是先遍历右子节点数,所以能保证该层添加的是最右边的一个节点的值。
8. 429.N叉树的层序遍历
给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例)。
/*
// Definition for a Node.
class Node {
public:
int val;
vector<Node*> children;
Node() {}
Node(int _val) {
val = _val;
}
Node(int _val, vector<Node*> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
public:
vector<vector<int>> levelOrder(Node* root) {
vector<vector<int>> result;
queue<Node * > que;
if(!root)
return result;
que.push(root);
while(!que.empty()){
vector<int> temp;
int size = que.size();
for(int i = 0 ; i < size ; i++){
Node * node = que.front();
que.pop();
temp.push_back(node->val);
for(auto it : node->children){
que.push(it);
}
}
result.push_back(temp);
}
return result;
}
};
基本和二叉树DFS一样。
9. 117.填充每个节点的下一个右侧节点指针II
给定一个二叉树
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
/*
// Definition for a Node.
class Node {
public:
int val;
Node* left;
Node* right;
Node* next;
Node() : val(0), left(NULL), right(NULL), next(NULL) {}
Node(int _val) : val(_val), left(NULL), right(NULL), next(NULL) {}
Node(int _val, Node* _left, Node* _right, Node* _next)
: val(_val), left(_left), right(_right), next(_next) {}
};
*/
class Solution {
public:
Node* connect(Node* root) {
queue<Node*> que;
que.push(root);
if(!root) return root;
while(!que.empty()){
int size = que.size();
for(int i = 0 ; i < size ; i++){
Node * node = que.front();
que.pop();
if(i != size - 1) {
node->next = que.front();
}else{
node->next = NULL;
}
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
}
return root;
}
};
同样是DFS的应用。
这里写代码的时候出现了一个报错member access within misaligned address
感觉其实就是相当于空指针
之前的代码:que有时候会为空 front会不存在,所以报的错
if(que.front()) {
node->next = que.front();
}else{
node->next = NULL;
}
10. 226 翻转二叉树
给你一棵二叉树的根节点 root
,翻转这棵二叉树,并返回其根节点。
/**
* 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 == nullptr)
return root;
// 感觉上 DFS和BFS都可以实现翻转
// 到叶节点结束
TreeNode * temp = root->left;
root->left = root->right;
root->right = temp;
invertTree(root->left);
invertTree(root->right);
return root;
}
};
本题这里使用了递归的思想,因为考虑到要遍历二叉树,那逃不开DFS和BFS,而使用DFS最简便的方式是使用递归,所以首先设置了递归结束的条件,即root == nullptr ,这是遍历到叶节点的左右子节点就返回,然后非空节点就进行左右互换,对于叶节点来说,左右节点均为null所以互换没影响,所以可以将叶节点和非叶节点统一处理。我这里相当于是前序遍历的思路。先处理根节点,再往下处理。
11. 101. 对称二叉树
给你一个二叉树的根节点 root
, 检查它是否轴对称。
/**
* 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) {
vector<int> vec1;
vector<int> vec2;
preTraversal1(root,vec1);
preTraversal2(root,vec2);
return vec1==vec2;
}
void preTraversal1(TreeNode* root,vector<int> & vec){
if(root == nullptr){
vec.push_back(101);
return;
}
vec.push_back(root->val);
preTraversal1(root->left,vec);
preTraversal1(root->right,vec);
}
void preTraversal2(TreeNode* root,vector<int> & vec){
if(root == nullptr){
vec.push_back(101);
return;
}
vec.push_back(root->val);
preTraversal2(root->right,vec);
preTraversal2(root->left,vec);
}
};
检查轴对称,本题一开始没有发现什么思路,后面结合二叉树的两种遍历方式,发现可以构造一个数组vector来判断是否轴对称,数组内存储各个节点的值,由于是轴对称,所以必定从两个方向遍历二叉树得出的vector数组是一样的,按照这个思路做题。
还有一个思路,可以先翻转,然后直接判断两个二叉树是否相等就行了。
但都显得比较麻烦,还有一个思路是用双指针,在二叉树上游走判断。
递归三部曲的使用:
- 确定递归函数的参数和返回值
- 确定终止条件
- 确定单层递归的逻辑
递归三部曲:
- 确定递归函数的参数和返回值
因为我们要比较的是根节点的两个子树是否是相互翻转的,进而判断这个树是不是对称树,所以要比较的是两个树,参数自然也是左子树节点和右子树节点。
返回值自然是bool类型。
代码如下:
bool compare(TreeNode* left, TreeNode* right)
- 确定终止条件
要比较两个节点数值相不相同,首先要把两个节点为空的情况弄清楚!否则后面比较数值的时候就会操作空指针了。
节点为空的情况有:(注意我们比较的其实不是左孩子和右孩子,所以如下我称之为左节点右节点)
- 左节点为空,右节点不为空,不对称,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, 因为我们把以上情况都排除之后,剩下的就是 左右节点都不为空,且数值相同的情况。
- 确定单层递归的逻辑
此时才进入单层递归的逻辑,单层递归的逻辑就是处理 左右节点都不为空,且数值相同的情况。
- 比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。
- 比较内测是否对称,传入左节点的右孩子,右节点的左孩子。
- 如果左右都对称就返回true ,有一侧不对称就返回false 。
本题的递归法:
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);
}
};
12 . 100. 相同的树
/**
* 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 isSameTree(TreeNode* p, TreeNode* q) {
if(p == nullptr && q == nullptr)
return true;
if((p == nullptr && q != nullptr) ||(p != nullptr && q == nullptr) )
return false;
// 迭代法
queue<TreeNode*> que1;
queue<TreeNode*> que2;
que1.push(p);
que2.push(q);
while(!que1.empty() && !que2.empty()){
TreeNode * t1 = que1.front();que1.pop();
TreeNode * t2 = que2.front();que2.pop();
if(t1->val != t2->val) return false;
if(t1->left == nullptr && t2->left != nullptr) return false;
if(t1->right == nullptr && t2->right != nullptr) return false;
if(t1->left != nullptr && t2->left == nullptr) return false;
if(t1->right != nullptr && t2->right == nullptr) return false;
if(t1->left != nullptr){
que1.push(t1->left);
que2.push(t2->left);
}
if(t1->right != nullptr){
que1.push(t1->right);
que2.push(t2->right);
}
}
return true;
}
};
用的是迭代法求的
13.104.二叉树的最大深度
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/maximum-depth-of-binary-tree
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
/**
* 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:
int maxDepth(TreeNode* root) {
int count=0;
return preOrderTraversal(root,count);
}
int preOrderTraversal(TreeNode * root,int count){
if(root==nullptr) return count;
count++;
int count1 = preOrderTraversal(root->left,count);
int count2 = preOrderTraversal(root->right,count);
return max(count1,count2);
}
};
/**
* 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:
int maxDepth(TreeNode* root) {
if (root == NULL) return 0;
queue<TreeNode*> que;
que.push(root);
int result = 0;
while(!que.empty()){
int size = que.size();
for(int i = 0 ; i < size ; i++){
TreeNode* node = que.front();
que.pop();
if(node->left !=nullptr) que.push(node->left);
if(node->right!=nullptr) que.push(node->right);
}
result++;
}
return result;
}
};
由于求深度,所以用深度优先遍历,这里采用的是前序遍历,传入的count值,是从根节点到当前节点的深度,最后,只需要返回左右节点的最大深度即可。这里就有点像是回溯了,但又不太一样,这边不需要对count作回溯处理,因为传递的都是副本,并不会对其他方法造成影响。
14. • 559.n叉树的最大深度
给定一个 N 叉树,找到其最大深度。
最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。
N 叉树输入按层序遍历序列化表示,每组子节点由空值分隔(请参见示例)。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/maximum-depth-of-n-ary-tree
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
/*
// Definition for a Node.
class Node {
public:
int val;
vector<Node*> children;
Node() {}
Node(int _val) {
val = _val;
}
Node(int _val, vector<Node*> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
public:
int maxDepth(Node* root) {
return pre(root,0);
}
int pre(Node * root,int count){
if(root==nullptr) return count;
count++;
int result = count;
for(auto node : root->children){
result = max(pre(node,count),result);
}
return result;
}
};
和上题基本一样。
15. 111.二叉树的最小深度
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
**说明:**叶子节点是指没有子节点的节点。
/**
* 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:
int minDepth(TreeNode* root) {
if(root == nullptr) return 0;
return loop(root,0);
}
int loop(TreeNode* root,int count){
count++;
// 为叶节点
if(!root->left && !root->right) return count;
if(root->left && root->right)
return min(loop(root->right,count),loop(root->left,count));
if(!root->left) return loop(root->right,count);
if(!root->right) return loop(root->left,count);
return 0;
}
};
本题求最小深度和前面的求最大深度有所不同,因为最小深度指的是到叶节点的深度,也就是说,如果一个节点左子节点为空,但右子节点不为空,参考只有右节点的树,所以逻辑略有不同,本题我的思路是将终止条件定义为叶节点,单层的逻辑分为不同的情况,只有左子、只有右子、以及左右子均有的情况。属于前序遍历。
16. 222.完全二叉树的节点个数
给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。
完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2h 个节点。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/count-complete-tree-nodes
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
/**
* 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:
int countNodes(TreeNode* root) {
if(root==nullptr) return 0;
int l = 0 , r = 0;
TreeNode* left = root->left;
TreeNode* right = root->right;
while(left){
l++;
left = left->left;
}
while(right){
r++;
right = right->right;
}
if(l == r) return pow(2,l + 1) - 1;
return countNodes(root->left) + countNodes(root->right) + 1;
}
};
本题虽然可以直接遍历一下二叉树求解,但是面对完全二叉树,有一个新的做法,就是利用完全二叉树的性质来写,满二叉树的节点个数为 E = 2 h − 1 E = 2^h-1 E=2h−1。而完全二叉树中有若干满二叉树。抓住这一点来解题。
https://programmercarl.com/0222.完全二叉树的节点个数.html
17.110.平衡二叉树
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
/**
* 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 isBalanced(TreeNode* root) {
if(root == nullptr) return true;
if(abs(loop(root->left,0) - loop(root->right,0)) > 1) return false;
bool b1 = isBalanced(root->left) ;
bool b2 = isBalanced(root->right);
return b1 && b2;
}
int loop(TreeNode* root,int count){
if(root==nullptr) return count;
count++;
return max(loop(root->left,count),loop(root->right,count));
}
};
/**
* 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:
int getHeight(TreeNode* node) {
if (node == NULL) {
return 0;
}
int leftHeight = getHeight(node->left);
if (leftHeight == -1) return -1;
int rightHeight = getHeight(node->right);
if (rightHeight == -1) return -1;
return abs(leftHeight - rightHeight) > 1 ? -1 : 1 + max(leftHeight, rightHeight);
}
bool isBalanced(TreeNode* root) {
return getHeight(root) == -1 ? false : true;
}
};
本题中相当于使用了两个递归算法,loop方法用于计算root节点的左右子树高度差,isBalanced用于计算所有节点是否为平衡树。
第二种方法的思路和第一种略有不同,两种方法的终止条件都为0,第二种方法的返回值为当前二叉树的高度,第一种的返回值是当前节点为根的子树是否为平衡树;第二种方法只要有-1则证明有不平衡的子树,则直接为false;其实也差不多,loop方法用于求树的高度。
18. 257. 二叉树的所有路径
给你一个二叉树的根节点 root
,按 任意顺序 ,返回所有从根节点到叶子节点的路径。
叶子节点 是指没有子节点的节点。
/**
* 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<string> result;
vector<string> binaryTreePaths(TreeNode* root) {
vector<int> vec;
pre(root,vec);
return result;
}
void pre(TreeNode* root,vector<int>& vec){
if(root==nullptr) return;
// 终止条件 叶节点
if(!root->left && !root->right){
string temp;
for(auto v:vec){
temp += to_string(v);
temp += "->";
}
temp += to_string(root->val);
result.push_back(temp);
return ;
};
vec.push_back(root->val);
pre(root->left,vec);
pre(root->right,vec);
vec.pop_back();
}
};257. 二叉树的所有路径
本题我的思路是用到了回溯方法,用vec存储节点值,若递归到叶节点则存储最终的result;并回溯。
19. 404.左叶子之和
给定二叉树的根节点 root
,返回所有左叶子之和。
/**
* 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:
int sumOfLeftLeaves(TreeNode* root) {
int sum = 0;
loop(root,sum,0);
return sum;
}
// flag = -1 左 ;flag = 1 右
void loop(TreeNode* root,int & sum,int flag){
if(root==nullptr) return;
if(!root->left && !root->right && flag == -1){
sum+=root->val;
return;
}
loop(root->left,sum,-1);
loop(root->right,sum,1);
}
};404.左叶子之和
本题主要是判断左右叶节点,加一个flag判断即可。
20. 513.找树左下角的值
给定一个二叉树的 根节点 root
,请找出该二叉树的 最底层 最左边 节点的值。
假设二叉树中至少有一个节点。
/**
* 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:
int findBottomLeftValue(TreeNode* root) {
// 层序遍历
int result;
queue<TreeNode*> que;
que.push(root);
while(!que.empty()){
int size = que.size();
TreeNode* temp = que.front();
que.pop();
if(temp->left) que.push(temp->left);
if(temp->right) que.push(temp->right);
result = temp->val;
for(int i = 1 ; i < size ;i++){
TreeNode* node = que.front();
que.pop();
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
}
return result;
}
};
/**
* 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:
int result;
int height;
int findBottomLeftValue(TreeNode* root) {
// 试试递归求最大深度 然后再找最左叶节点。
height = 0;
loop(root,0);
return result;
}
int loop(TreeNode* root,int count){
if(root == nullptr) return count;
// 本层逻辑的处理
count++;
if(count>height){
height = count; //更新最大高度
result = root->val;
}
loop(root->left,count);
loop(root->right,count);
return -1;
}
};
层序遍历的话非常简单,每层第一个即可。第二种方法使用的依旧是递归遍历,这边由于是要求最底层的最左边的叶节点,所以直接采用了前序遍历,并用上height
这一全局变量,因为对于前序遍历来说,每一层第一个被遍历到的节点一定是该层的最左边的节点,所以,当深度第一次大于height时,正在递归的一定是该层最左节点,所以记录下来,并将height+1,这样,该层就只有最左节点被记录下来。
21. 路径总和
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。
叶子节点 是指没有子节点的节点。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/path-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
/**
* 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 hasPathSum(TreeNode* root, int targetSum) {
return loop(root,targetSum,0);
}
bool loop(TreeNode* root,int targetSum,int sum){
bool flag = false;
if(root == nullptr) return false;
sum+=root->val;
if(!root->left && !root->right){
if(sum == targetSum) flag = true;
}
flag = flag || loop(root->left,targetSum,sum);
flag = flag || loop(root->right,targetSum,sum);
return flag;
}
};
本题的主要思路就是通过判断左子和右子是否有符合条件的叶节点来判断,终止条件为当前节点为空,判断是判断叶节点是否符合条件,最后通过或操作来返回,只要有true则最终返回true。
22. 113. 路径总和ii
给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
叶子节点 是指没有子节点的节点。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/path-sum-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
/**
* 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>> result;
vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
vector<int> temp;
loop(root,targetSum,0,temp);
return result;
}
void loop(TreeNode* root,int targetSum,int sum,vector<int>& temp){
if(root == nullptr) return;
sum+=root->val;
temp.push_back(root->val);
if(!root->left && !root->right){
if(sum == targetSum){
result.push_back(temp);
temp.pop_back();
return;
}
}
loop(root->left,targetSum,sum,temp);
loop(root->right,targetSum,sum,temp);
temp.pop_back();
}
};
和上一题差不多,多加了个存储路径的结果值。