二叉树的层序遍历
给你二叉树的根节点
root
,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
思路:
接受二叉树每层的节点:
- 利用queue先进先出的性质,保证各个node的进入顺序与层序遍历顺序保持一致
确定节点的层数:
- 利用map接受节点并记录对应层数,弹出时根据层数放置到答案中
大体流程(循环):
- cycle:记录que的头节点,并将其val插入到对应的vector中后pop;按照先左后右的顺序,将tmp的非空子节点push到que中并用map记录子节点的层数;最后删除tmp在map中的记录;直到que为空(全部node都已遍历完)
代码:
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode*> que;
vector<vector<int>>vec;
map<TreeNode*,int> Node_lel;
if(root==nullptr)return vec;
vec.resize(2000);
que.push(root);
Node_lel[root]=0;
TreeNode* tmp;
while(que.size()){
tmp=que.front();
vec[Node_lel[tmp]].push_back(tmp->val);
que.pop();
if(tmp->left){
que.push(tmp->left);
Node_lel[tmp->left]=Node_lel[tmp]+1;
}
if(tmp->right){
que.push(tmp->right);
Node_lel[tmp->right]=Node_lel[tmp]+1;
}
if(que.size()==0){
vec.resize(Node_lel[tmp]+1);
}
Node_lel.erase(tmp+1);
}
return vec;
}
};
优化思路:
优化queue的性能:
- 利用静态数组模拟queue,提升进出效率
优化map:
分析queue不同元素的层数发现,元素的层数之差不超过一且具有相同层数的元素紧挨在一起,如此只需记录queue头的层数(level)并记录同一层数的node个数(cnt)即可替代map
- 初始的level已知:0;初始的cnt已知:1
- 遍历完level层的node:流程与之前类似,每遍历一个node,--cnt。
- level和cnt的更新:当cnt为0时,level和cnt同步更新。此时queue中原属于level层的node已全部弹出,只剩下他们的子节点,由此更新cnt和level
优化代码:
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>>vec;
if(root==nullptr)return vec;
vec.resize(2000);
int l=0,r=0;
que[r++]=root;
int cnt=1,level=0;
TreeNode* tmp;
while(cnt--){
tmp=que[l++];
vec[level].push_back(tmp->val);
if(tmp->left){
que[r++]=tmp->left;
}
if(tmp->right){
que[r++]=tmp->right;
}
if(cnt==0){
++level;
cnt=r-l;
}
}
vec.resize(level);
return vec;
}
static TreeNode** que;
};
TreeNode** Solution::que=new TreeNode*[2001];
二、二叉树的锯齿形层序遍历
思路:
大体流程与层序遍历一致,在最后将vec对应的vec进行反转
代码:
class Solution {
public:
vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
queue<TreeNode*> que;
vector<vector<int>>vec;
if(root==nullptr)return vec;
vec.resize(2000);
que.push(root);
int cnt=1,level=0;
TreeNode* tmp;
while(cnt--){
tmp=que.front();
vec[level].push_back(tmp->val);
que.pop();
if(tmp->left){
que.push(tmp->left);
}
if(tmp->right){
que.push(tmp->right);
}
if(cnt==0){
++level;
cnt=que.size();
}
}
vec.resize(level);
for(int i=1;i<level;i+=2){
reverse(vec[i].begin(),vec[i].end());
}
return vec;
}
};
优化思路:
优化queue:
- 利用静态数组模仿queue,优化pop和push;
优化反转:
- 利用静态数组在每次要进入下一层时,将全部ele整体压进vec中;同时设置变量flag,适时反向压入
优化代码:
class Solution {
public:
vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
vector<vector<int>>vec;
if(root==nullptr)return vec;
vec.resize(2000);
int l=0,r=0;
que[r++]=root;
int cnt=1,level=0;
bool flag=0;
TreeNode* tmp=root;
vec[level].push_back(tmp->val);
while(cnt--){
tmp=que[l++];
if(tmp->left){
que[r++]=tmp->left;
}
if(tmp->right){
que[r++]=tmp->right;
}
if(cnt==0){
flag=!flag;
++level;
vec[level].resize(r-l);
if(flag){
for(int i=r-1,k=0;i>=l;--i,++k){
vec[level][k]=que[i]->val;
}
}else{
for(int i=l,k=0;i<r;++i,++k){
vec[level][k]=que[i]->val;
}
}
cnt=r-l;
}
}
vec.resize(level);
return vec;
}
static TreeNode** que;
};
TreeNode** Solution::que=new TreeNode*[2001];
三、二叉树最大宽度
给你一棵二叉树的根节点
root
,返回树的 最大宽度 。树的 最大宽度 是所有层中最大的 宽度 。
每一层的 宽度 被定义为该层最左和最右的非空节点(即,两个端点)之间的长度。将这个二叉树视作与满二叉树结构相同,两端点间会出现一些延伸到这一层的
null
节点,这些null
节点也计入长度。题目数据保证答案将会在 32 位 带符号整数范围内
思路:
大体流程与层序遍历一致,但在过程中需要给各个node进行编号,queue的模版参数也改为pair<Node*,int>。在每层开始时统计尾到头的编号差值。
代码:
class Solution {
public:
int widthOfBinaryTree(TreeNode* root) {
queue<pair<TreeNode*,unsigned>> que;
if(root==nullptr)return 0;
que.push({root,1});
unsigned cnt=1,ret=1;
pair<TreeNode*,unsigned> tmp;
while(cnt--){
tmp=que.front();
que.pop();
if(tmp.first->left){
que.push({tmp.first->left,2*tmp.second});
}
if(tmp.first->right){
que.push({tmp.first->right,2*tmp.second+1});
}
if(cnt==0&&que.size()){
ret=max(ret,que.back().second-que.front().second+1);
cnt=que.size();
}
}
return ret;
}
};
注:
由于数据量过大使用int存储编号会溢出,而利用unsigned的性质(溢出后从零开始计数)和题目提示答案大小在32位内,可知需要用unsigned存储编号
优化思路:
优化queue:
- 利用两个静态数组分别存储节点和编号或一个静态数组模仿queue,优化pop和push;
优化代码:
与之前类似不再重复
四、二叉树的最大深度
给定一个二叉树
root
,返回其最大深度。二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。
思路:
basecase(基础条件):若root为空,return 0;
recur(递归):return max(root->left的最大深度,root->right的最大深度)+1;
代码:
class Solution {
public:
int maxDepth(TreeNode* root) {
if(!root)return 0;
return max(maxDepth(root->left),maxDepth(root->right))+1;
}
};
五、二叉树的最小深度
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点
思路:
basecase(基础条件):
- 若root为空,return 0;
- 若root左空右不空,return root->right的最小深度+1;
- 若root右空左不空,return root->left的最小深度+1;
- 若root左右皆空,return 1;
recur(递归):
return min(root->right的最小深度,root->left的最小深度)+1;
代码:
class Solution {
public:
int minDepth(TreeNode* root) {
if(!root)return 0;
if(!root->left&&!root->right)return 1;
if(!root->left&&root->right)return minDepth(root->right)+1;
if(!root->right&&root->left)return minDepth(root->left)+1;
return min(minDepth(root->right),minDepth(root->left))+1;
}
};
六、序列化与反序列化二叉树 (先序实现)
序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。
请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
提示: 输入输出格式与 LeetCode 目前使用的方式一致,详情请参阅 LeetCode 序列化二叉树的格式。你并非必须采取这种方式,你也可以采用其他的方法解决这个问题。
思路:
序列化:
- 流程与先序遍历大体先一致,在node压入时将其加入string,并添加“,”以分割不同节点,遇到空node可用字符“#”标识
反序列化:
- 整体流程与先序遍历一致,在开始前需将string以分隔符“,”分割成vector<string>(C++中无split函数)。遍历vector,在进queue时利用stoi确定各个node的val
代码:
class Codec {
public:
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
// return "1,2,#,#,3,4,#,#,5,#,#,";
string str;
if (!root)
return "#,";
stack<TreeNode*> stacks;
TreeNode* cur = root;
while (cur||stacks.size()) {
while (cur) {
stacks.push(cur);
str += to_string(cur->val);
str += ",";
cur = cur->left;
}
str += "#,";
if (stacks.size() == 0)
break;
cur = stacks.top();
stacks.pop();
cur = cur->right;
}
return str;
}
void split(const string& str, vector<string>& vec, const string& delim) {
if (str == "")
return;
int spos = 0;
int pos = str.find(delim);
int epos = str.size();
while (pos != string::npos) {
vec.push_back(str.substr(spos, pos - spos));
spos = pos + delim.size();
pos = str.find(delim, spos);
}
if (spos != epos) {
vec.push_back(str.substr(spos));
}
}
// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
if (data == "#,")
return nullptr;
vector<string> vec;
split(data, vec, ",");
int i = 1, len = vec.size();
TreeNode* root = new TreeNode(stoi(vec[0]));
TreeNode* cur = root;
stack<TreeNode*> stacks;
stacks.push(root);
while (i < len) {
while (i < len && vec[i++] != "#") {
TreeNode* tmp = new TreeNode(stoi(vec[i - 1]));
cur->left = tmp;
cur = tmp;
stacks.push(tmp);
}
while (i < len) {
if (stacks.size()) {
cur = stacks.top();
stacks.pop();
}
if (i < len && vec[i++] != "#") {
TreeNode* tmp = new TreeNode(stoi(vec[i - 1]));
cur->right = tmp;
cur = cur->right;
stacks.push(tmp);
break;
}
}
}
return root;
}
};
注:
可以通过先序或后序反序列化,而中序的序列化不具有唯一性,无法反序列化
七、序列化与反序列化二叉树 (层序实现)
思路:
序列化:
- 流程与层序遍历大体一致,在node弹出时将其压入string,并添加“,”以分割不同节点,遇到空可用字符“#”标识
反序列化:
- 整体流程与层序遍历一致,在开始前需将string以分隔符“,”分割成vector<string>(C++中无split函数)。遍历vector,在进queue时利用stoi确定各个node的val
代码:
class Codec {
public:
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
string str;
if (!root)
return "#";
queue<TreeNode*> que;
TreeNode* cur = root;
str += to_string(root->val);
str += ',';
que.push(root);
while (que.size()) {
cur = que.front();
que.pop();
if (cur->left) {
str += to_string(cur->left->val);
que.push(cur->left);
} else {
str += '#';
}
str += ',';
if (cur->right) {
str += to_string(cur->right->val);
que.push(cur->right);
} else {
str += '#';
}
str += ',';
}
return str;
}
void split(const string& str,vector<string>& vec, const string& delim) {
if(str=="")return;
int spos=0;
int pos=str.find(delim);
int epos=str.size();
while(pos!=string::npos){
vec.push_back(str.substr(spos,pos-spos));
spos=pos+delim.size();
pos=str.find(delim,spos);
}
if(spos!=epos){
vec.push_back(str.substr(spos));
}
}
// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
if (data==""||data == "#")
return nullptr;
vector<string> vec;
split(data,vec,",");
int i = 1, len = vec.size();
TreeNode* root = new TreeNode(stoi(vec[0]));
queue<TreeNode*> que;
que.push(root);
TreeNode* cur;
while (i < len) {
cur = que.front();
que.pop();
if (i < len && vec[i++] != "#") {
cur->left = new TreeNode(stoi(vec[i - 1]));
que.push(cur->left);
}
if (i < len && vec[i++] != "#") {
cur->right = new TreeNode(stoi(vec[i - 1]));
que.push(cur->right);
}
}
return root;
}
};
八、从前序与中序遍历序列构造二叉树
给定两个整数数组
preorder
和inorder
,其中preorder
是二叉树的先序遍历,inorder
是同一棵树的中序遍历,请构造二叉树并返回其根节点。
思路:
分析前序和中序遍历序列的特征:
- 前序:序列的开头一定是整颗二叉树的根节点,且任何一颗子树的root都在整颗子树序列范围的开头
- 中序:整颗二叉树的根节点将序列分为左右子树的序列,且任何一颗子树的root都将整颗子树序列分为左右子树的序列:左部分为左子树的序列,右部分为右子树的序列
大体流程(递归):
递归函数的返回值:
- 根据给定的前序序列范围和中序序列范围,构造二叉树并返回root
basecase:
- 范围违规(l>r),返回空节点
recur:
- 构造头结点:根据规定的前序范围的左端点
- 划分左右:根据题目提示无重复元素,对中序序列使用查找函数找到root,并以此确定左右范围
- 构造左右:递归构造
代码:
class Solution {
public:
TreeNode* build(vector<int>& preorder,int l1,int r1, vector<int>& inorder,int l2,int r2){
if(l1>r1)return nullptr;
TreeNode* root=new TreeNode(preorder[l1]);
vector<int>::iterator tmp=find(inorder.begin()+l2,inorder.begin()+r2,preorder[l1]);
int len1=tmp-inorder.begin()-l2;
//int len2=r
root->left=build(preorder,l1+1,l1+len1,inorder,l2,l2+len1-1);
root->right=build(preorder,l1+len1+1,r1,inorder,l2+len1+1,r2);
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
if(preorder.size()==0)return nullptr;
return build(preorder,0,preorder.size()-1,inorder,0,preorder.size()-1);
}
};
九、二叉树的完全性检验
给你一棵二叉树的根节点
root
,请你判断这棵树是否是一棵 完全二叉树 。在一棵 完全二叉树 中,除了最后一层外,所有层都被完全填满,并且最后一层中的所有节点都尽可能靠左。最后一层(第
h
层)中可以包含1
到2h
个节点。
思路:
大体流程与层序遍历一致,过程中对每个节点验证两个完全二叉树的特性:
- 节点不存在有右无左
- 若出现节点的子不全,则后续节点都为叶子结点(子全无)
代码:
class Solution {
public:
bool isCompleteTree(TreeNode* root) {
if(root==nullptr)return true;
int l=0,r=0;
que[r++]=root;
TreeNode* cur=root;
bool flag=false;
while(l<r){
cur=que[l++];
if(cur->left){
if(flag)return false;
que[r++]=cur->left;
}
if(cur->right){
if(flag||!cur->left)return false;
que[r++]=cur->right;
}
if(!cur->left||!cur->right){
flag=true;
}
}
return true;
}
static TreeNode** que;
};
TreeNode** Solution::que=new TreeNode*[110];
十、完全二叉树的节点个数
给你一棵 完全二叉树 的根节点
root
,求出该树的节点个数。完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第
h
层,则该层包含1~ 2h
个节点。
思路:
利用完全二叉树的特性,不断递归求子树的节点个数:
- 若root到其右树最左node的高度小于其到左树最左node,则总node数为右树+root的节点数(2^(H-1))和左树的节点数
- 若root到其右树最左node的高度等于其到左树最左node,则总node数为左树+root的节点数(2^H)和右树的节点数
代码:
class Solution {
public:
int countNodes(TreeNode* root) {
if(root==nullptr)return 0;
int h1=Height(root->left);
int h2=Height(root->right);
int ans=0;
if(h1>h2){
ans+=pow(2,h2);
return ans+countNodes(root->left);
}
ans+=pow(2,h1);
return ans+countNodes(root->right);
}
int Height(TreeNode* root){
if(root==nullptr)return 0;
int cnt=0;
while(root){
++cnt;
root=root->left;
}
return cnt;
}
};