本文记录我刷二叉树专题的笔记
二叉树原理这里不多说,主要讲解二叉树的一些题型,和做题上的小技巧和模板。
我刷了 100 多道二叉树的题,并且参考其它大佬的博客,把二叉树的题划分为几类:
其中,遍历是二叉树专题的基础,不像链表,主要以修改为核心,在树的题型中,多数还是以遍历为基础。
基础的 dfs 和 bfs,前中后序层序,都要滚瓜烂熟。
基础的 dfs 掌握递归,栈即可,栈的做法比较难,可以掌握双色标记,至于 morris 的方法,我觉得比较麻烦,就没有掌握。
在做题的过程中,dfs 的递归实现和层序非常高频。
比如 BST 的题型中,中序使用得非常多,而涉及到每层节点的题,则层序使用得比较多,当然也可以 dfs 同时带上层数。构造二叉树的题型,则多是后序,因为从第往上遍历构造会非常方便。
而序列化和反序列化的题,则掌握前中后遍历后左、右、根的顺序即可。
还有一些题型, dfs 的逆序也比较常见,比如 BST 中序是递增的,而中序逆序自然就是递减的,求第 k 大的问题就需要这么做。逆序在使用到栈的时候,也需要这么做,比如双色标记遍历法中,就是普通 dfs 的完全逆序。
至于做题的技巧,我认为不多,可以把根节点都修改为 root,这样比较方便。并且一些模板题,比如层序这种,可以不要复制,自己敲,很快就会非常熟练。
其它还有平衡树,字典树,堆这些树型结构的,则题型和本专题不太一样,所以不涉及。
二叉树的遍历是最基础的题型,也是许多二叉树题的基础。分为 dfs 和 bfs,dfs 有递归、栈、二色标记、Morris 几种实现, bfs 一般用队列,也可以递归巧妙的实现。
list
题意
前序打印二叉树
递归
/**
* 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 {
vector<int> res;
public:
vector<int> preorderTraversal(TreeNode* root) {
if (!root){
return res;
}
res.push_back(root->val);
preorderTraversal(root->left);
preorderTraversal(root->right);
return res;
}
};时间复杂度:,空间复杂度:
栈
/**
* 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){
return {};
}
stack<TreeNode*> s;
vector<int> res;
s.push(root);
while(!s.empty()){
auto t=s.top();
s.pop();
res.push_back(t->val);
if (t->right){
s.push(t->right);
}
if (t->left){
s.push(t->left);
}
}
return res;
}
};时间复杂度:,空间复杂度:
二色表示
/**
* 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) {
// val,color
stack<pair<TreeNode*,int>> s;
vector<int> res;
s.push({root,0});
while(!s.empty()){
auto t=s.top();
s.pop();
if (!t.first){
continue;
}
if (t.second==1){
res.push_back(t.first->val);
continue;
}
s.push({t.first->right,0});
s.push({t.first->left,0});
s.push({t.first,1});
}
return res;
}
};时间复杂度:,空间复杂度:
题意
前序打印二叉树
递归
/**
* 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 {
vector<int> res;
public:
vector<int> inorderTraversal(TreeNode* root) {
if (!root){
return res;
}
inorderTraversal(root->left);
res.push_back(root->val);
inorderTraversal(root->right);
return res;
}
};时间复杂度:,空间复杂度:
栈
/**
* 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) {
stack<TreeNode*> s;
vector<int> res;
while(root||!s.empty()){
while(root){
s.push(root);
root=root->left;
}
auto t=s.top();
s.pop();
res.push_back(t->val);
root=t->right;
}
return res;
}
};时间复杂度:,空间复杂度:
二色表示
/**
* 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) {
// val,color
stack<pair<TreeNode*,int>> s;
vector<int> res;
s.push({root,0});
while(!s.empty()){
auto t=s.top();
s.pop();
if (!t.first){
continue;
}
if (t.second==1){
res.push_back(t.first->val);
continue;
}
s.push({t.first->right,0});
s.push({t.first,1});
s.push({t.first->left,0});
}
return res;
}
};时间复杂度:,空间复杂度:
题意
前序打印二叉树
递归
/**
* 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 {
vector<int> res;
public:
vector<int> postorderTraversal(TreeNode* root) {
if (!root){
return res;
}
postorderTraversal(root->left);
postorderTraversal(root->right);
res.push_back(root->val);
return res;
}
};时间复杂度:,空间复杂度:
栈
/**
* 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*> s;
vector<int> res;
while(root||!s.empty()){
while(root){
s.push(root);
if (root->left){
root=root->left;
} else {
root=root->right;
}
}
auto t=s.top();
s.pop();
res.push_back(t->val);
if (!s.empty()&&t==s.top()->left){
root=s.top()->right;
} else {
root=nullptr;
}
}
return res;
}
};时间复杂度:,空间复杂度:
二色表示
/**
* 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) {
// val,color
stack<pair<TreeNode*,int>> s;
vector<int> res;
s.push({root,0});
while(!s.empty()){
auto t=s.top();
s.pop();
if (!t.first){
continue;
}
if (t.second==1){
res.push_back(t.first->val);
continue;
}
s.push({t.first,1});
s.push({t.first->right,0});
s.push({t.first->left,0});
}
return res;
}
};时间复杂度:,空间复杂度:
题意
层序遍历二叉树
分析
由于每层进行遍历,显然是 bfs,故使用队列实现
每次循环时,一层的元素都在队列中,所以队列的长度就是这一层的元素个数,这一层全取出来,然后再逐个 push 左右子节点即可。
队列 bfs
/**
* 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) {
queue<TreeNode*> q;
vector<vector<int>> res;
q.push(root);
while(root && !q.empty()){
vector<int> t;
int size=q.size();
while(size--){
root=q.front();
q.pop();
t.push_back(root->val);
if (root->left) {
q.push(root->left);
}
if (root->right){
q.push(root->right);
}
}
res.push_back(t);
}
return res;
}
};时间复杂度:,空间复杂度:
递归 dfs
这题虽然要层序遍历的结果,但是实际上只要结果是这样即可,过程我们也可以 dfs 来实现,dfs 时,都是 left,right 的顺序,所以在宏观上每一层的顺序仍然没有改变。换句话说,遍历到每一个节点时,只要直到它是哪一层的,然后放到哪一层去即可。
故我们可以在 dfs 过程中,把这个节点的层数也传上去,然后遍历的时候 push 到这一层中去即可。
/**
* 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 {
vector<vector<int>> res;
public:
vector<vector<int>> levelOrder(TreeNode* root) {
dfs(root,0);
return res;
}
void dfs(TreeNode *root,int level){
if (!root){
return;
}
if (res.size()<level+1){
res.push_back({});
}
res[level].push_back(root->val);
dfs(root->left,level+1);
dfs(root->right,level+1);
}
};时间复杂度:,空间复杂度:
题意
给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
分析
reverse(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>> levelOrderBottom(TreeNode* root) {
queue<TreeNode*> q;
vector<vector<int>> res;
q.push(root);
while(root && !q.empty()){
vector<int> t;
int size=q.size();
while(size--){
root=q.front();
q.pop();
t.push_back(root->val);
if (root->left) {
q.push(root->left);
}
if (root->right){
q.push(root->right);
}
}
res.push_back(t);
}
return {res.rbegin(),res.rend()};
}
};题意
给你二叉树的根节点 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>> zigzagLevelOrder(TreeNode* root) {
queue<TreeNode*> q;
vector<vector<int>> res;
bool lr=true;
q.push(root);
while(root && !q.empty()){
vector<int> t;
int size=q.size();
while(size--){
root=q.front();
q.pop();
t.push_back(root->val);
if (root->left) {
q.push(root->left);
}
if (root->right){
q.push(root->right);
}
}
if (!lr){
reverse(t.begin(),t.end());
}
lr=!lr;
res.push_back(t);
}
return res;
}
};题意
给你二叉树的根结点 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 {
vector<vector<int>> res;
map<int,vector<pair<int,int>>> m;
public:
vector<vector<int>> verticalTraversal(TreeNode* root) {
pre(root,0,0);
for (auto i:m){
sort(i.second.begin(),i.second.end());
vector<int> t;
for (auto j=i.second.begin();j!=i.second.end();j++){
t.push_back((*j).second);
}
res.push_back(t);
}
return res;
}
void pre(TreeNode *root,int r,int c){
if (!root){
return;
}
m[c].push_back({r,root->val});
pre(root->left,r+1,c-1);
pre(root->right,r+1,c+1);
}
};题意
给定一个 n 叉树的根节点 root ,返回 其节点值的 前序遍历 。
分析
和二叉树前序遍历类似,先读取值,再以此递归子节点
效率
时间复杂度:
空间复杂度:
代码
class Solution {
vector<int> res;
public:
vector<int> preorder(Node* root) {
pre(root);
return res;
}
void pre(Node *root){
if (!root){
return;
}
res.push_back(root->val);
for (auto i:root->children){
pre(i);
}
}
};题意
给定一个 n 叉树的根节点 root ,返回 其节点值的 后序遍历 。
分析
和二叉树后序遍历类似,先以此递归子节点,再读取值
效率
时间复杂度:
空间复杂度:
代码
class Solution {
vector<int> res;
public:
vector<int> postorder(Node* root) {
post(root);
return res;
}
void post(Node *root){
if (!root){
return;
}
for (auto i:root->children){
post(i);
}
res.push_back(root->val);
}
};题意
给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。
分析
和普通的二叉树层序遍历区别不大,只是二叉树是 push 左右子节点,而 n 叉则把所有节点都 push 进队列。
效率
时间复杂度:
空间复杂度:
代码
class Solution {
public:
vector<vector<int>> levelOrder(Node* root) {
vector<vector<int>> res;
vector<int> n;
queue<Node*> q;
q.push(root);
while(root&&!q.empty()){
int size=q.size();
n.clear();
while(size--){
auto t=q.front();
q.pop();
n.push_back(t->val);
for (auto i:t->children){
if (i){
q.push(i);
}
}
}
res.push_back(n);
}
return res;
}
};题意
给定一棵二叉树,设计一个算法,创建含有某一深度上所有节点的链表(比如,若一棵树的深度为 D,则会创建出 D 个链表)。返回一个包含所有深度的链表的数组。
分析
题目的意思就是每一层建立一个链表,直接层序遍历即可
效率
时间复杂度:
空间复杂度:
代码
class Solution {
public:
vector<ListNode*> listOfDepth(TreeNode* root) {
vector<ListNode*> res;
queue<TreeNode*> q;
q.push(root);
while(root && !q.empty()){
int size=q.size();
ListNode *dummy = new ListNode();
ListNode *cur=dummy;
while(size--){
auto t=q.front();
q.pop();
cur->next=new ListNode(t->val);
cur=cur->next;
if (t->left){
q.push(t->left);
}
if (t->right){
q.push(t->right);
}
}
res.push_back(dummy->next);
}
return res;
}
};构建二叉树的题是指已经知道了遍历的结果,然后重建原本的二叉树。
list
题意
给定两个整数数组 preorder 和 inorder ,其中 preorder 是二叉树的先序遍历, inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。
分析
前序遍历的结果是:根左右
中序遍历的结果是:左根右
所以对于当前节点,前序遍历第一个元素来构建即可。对于左孩子,找到前中序遍历结果的两个左子数组,同理右孩子即可。
优化
也可以不用构建临时数组,传索引原地也行
效率
时间复杂度:
空间复杂度:
代码
临时数组
/**
* 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* buildTree(vector<int>& pre, vector<int>& in) {
if (pre.empty()){
return nullptr;
}
TreeNode *root=new TreeNode(pre[0]);
vector<int> leftPre,leftIn,rightPre,rightIn;
bool f=true;
for (auto &i:in){
if (i==pre[0]){
f=false;
continue;
}
if (f){
leftIn.push_back(i);
continue;
}
rightIn.push_back(i);
}
for (int i=1;i<pre.size();i++){
if (i-1<leftIn.size()){
leftPre.push_back(pre[i]);
continue;
}
rightPre.push_back(pre[i]);
}
root->left=buildTree(leftPre,leftIn);
root->right=buildTree(rightPre,rightIn);
return 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* buildTree(vector<int>& pre, vector<int>& in) {
return buildNode(pre,0,pre.size()-1,in,0,in.size()-1);
}
TreeNode* buildNode(vector<int>& pre,int pbegin,int pend, vector<int>& in,int ibegin,int iend) {
if (pbegin>pend||ibegin>iend||pend<0||iend<0){
return nullptr;
}
TreeNode *root=new TreeNode(pre[pbegin]);
int index;
for (int i=ibegin;i<iend;i++){
if (in[i]==pre[pbegin]){
index=i;
break;
}
}
root->left=buildNode(pre,pbegin+1,pbegin+index-ibegin,in,ibegin,index-1);
root->right=buildNode(pre,pbegin+index-ibegin+1,pend,in,index+1,iend);
return root;
}
};题意
给定两个整数数组,preorder 和 postorder ,其中 preorder 是一个具有 无重复 值的二叉树的前序遍历,postorder 是同一棵树的后序遍历,重构并返回二叉树。
分析
前序遍历的结果是:根左右
后序遍历的结果是:左右根
所以,对于当前节点来说,根据前序遍历的第一个元素建立。然后分别建立左右孩子节点,问题在于怎么把左右节点的不同遍历结果分开。
比如 preorder = [1,2,4,5,3,6,7], postorder = [4,5,2,6,7,3,1]
去除当前元素 1 后,变成 preorder = [2,4,5,3,6,7], postorder = [4,5,2,6,7,3]
则 preorder 的第一个元素 2 是左子树的根,以此划分后序 postorder,故 4,5,2 是左子树的节点,剩下 6,7,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:
TreeNode* constructFromPrePost(vector<int>& pre, vector<int>& post) {
if (pre.empty()){
return nullptr;
}
if (pre.size()==1){
return new TreeNode(pre[0]);
}
vector<int> leftPre,leftPost,rightPre,rightPost;
int i;
TreeNode* root=new TreeNode(pre[0]);
for (i=pre.size()-1;i>0;i--){
if (pre[i]==post[post.size()-2]) {
break;
}
}
leftPre={pre.begin()+1,pre.begin()+i};
rightPre={pre.begin()+i,pre.end()};
leftPost={post.begin(),post.begin()+post.size()-pre.size()+i-1};
rightPost={post.begin()+post.size()-pre.size()+i-1,post.end()-1};
root->left=constructFromPrePost(leftPre,leftPost);
root->right=constructFromPrePost(rightPre,rightPost);
return root;
}
};题意
给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。
分析
中序遍历的结果是:左根右
后序遍历的结果是:左右根
当前节点,根据后序遍历的尾元素建立,然后将左右孩子节点的遍历结果拆分。
拆分方式如下:
根据后序最后一个元素作为根,然后去中序遍历查找根,之前的元素为左孩子,右边的为右孩子。
效率
时间复杂度:
空间复杂度:
代码
/**
* 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* buildTree(vector<int>& in, vector<int>& post) {
if (in.empty()){
return nullptr;
}
if (in.size()==1){
return new TreeNode(in[0]);
}
vector<int> leftIn,leftPost,rightIn,rightPost;
int i;
TreeNode* root=new TreeNode(post[post.size()-1]);
for (i=0;i<in.size();i++){
if (in[i]==post[post.size()-1]){
break;
}
}
leftIn={in.begin(),in.begin()+i};
rightIn={in.begin()+i+1,in.end()};
leftPost={post.begin(),post.begin()+i};
rightPost={post.begin()+i,post.end()-1};
root->left=buildTree(leftIn,leftPost);
root->right=buildTree(rightIn,rightPost);
return root;
}
};题意
给你二叉树的根结点 root ,请你将它展开为一个单链表:
展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。
展开后的单链表应该与二叉树 先序遍历 顺序相同。
新树
直接前序遍历,然后遍历的时候在右子树新建节点即可。
/**
* 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 {
TreeNode *now;
TreeNode *head;
public:
void flatten(TreeNode* root) {
if (!root){
return;
}
head=new TreeNode();
now=head;
pre(root);
*root=*(head->right);
}
void pre(TreeNode* root){
if (!root){
return;
}
now->right=new TreeNode(root->val);
now=now->right;
pre(root->left);
pre(root->right);
}
};时间复杂度:,空间复杂度:
原地
新建树非常好理解,但空间复杂度有点高,我们也可以原地实现,基本思想就是前序的逆序+头插法。
比如 1,2,3,4,5 的树,前序就是 1,2,4,5,3,而逆序则是 3,5,4,2,1.即
dfs(root->right)
dfs(root->left)
cout<<root->val<<endl;的输出就是 3,5,4,3,1.
假设某个时刻之前的节点已经建立好,并且头指针为 pre,当前节点为 root,要头插,故直接 root→right=pre,pre=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 {
TreeNode *pre;
public:
void flatten(TreeNode* root) {
if (!root){
return;
}
flatten(root->right);
flatten(root->left);
root->right=pre;
root->left=nullptr;
pre=root;
}
};题意
有序链表转 BST
分析
先转数组
先转成数组,然后按照 108 题的方法构建。转成数组的主要目的就是减少寻找中间节点的时间。
class Solution {
public:
TreeNode* sortedListToBST(ListNode* head) {
vector<int> nums;
while(head){
nums.push_back(head->val);
head=head->next;
}
return sortedArrayToBST(nums);
}
TreeNode* sortedArrayToBST(vector<int>& nums) {
if (nums.empty()){
return nullptr;
}
TreeNode*root=new TreeNode(nums[nums.size()/2]);
vector<int> l={nums.begin(),nums.begin()+nums.size()/2};
vector<int> r={nums.begin()+nums.size()/2+1,nums.end()};
root->left=sortedArrayToBST(l);
root->right=sortedArrayToBST(r);
return root;
}
};时间复杂度:,空间复杂度:
直接转
和转换为数组差不多,找中间节点作为根,前后分别作为左右。找中间节点的方法即双指针。
class Solution {
public:
TreeNode* sortedListToBST(ListNode* head) {
if (!head){
return nullptr;
}
if (!head->next){
return new TreeNode(head->val);
}
ListNode *fast=head;
ListNode *slow=head;
ListNode *pre=nullptr;
while(fast&&fast->next){
fast=fast->next->next;
pre=slow;
slow=slow->next;
}
if (pre){
pre->next=nullptr;
}
TreeNode *root=new TreeNode(slow->val);
root->left=sortedListToBST(head);
root->right=sortedListToBST(slow->next);
return root;
}
};时间复杂度:,空间复杂度:
题意
根据升序数组建立 BST
分析
要建立 BST,显然需要平衡,即左右子树的高度要尽可能近,那么选取根节点时,就要使得左右节点尽可能一样多。故选取中点作为根,左右作为孩子节点即可。
效率
时间复杂度:
空间复杂度:
代码
/**
* 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* sortedArrayToBST(vector<int>& nums) {
if (nums.empty()){
return nullptr;
}
TreeNode*root=new TreeNode(nums[nums.size()/2]);
vector<int> l={nums.begin(),nums.begin()+nums.size()/2};
vector<int> r={nums.begin()+nums.size()/2+1,nums.end()};
root->left=sortedArrayToBST(l);
root->right=sortedArrayToBST(r);
return root;
}
};题意
前序构造 BST
分析
BST 中序是有序的,故对前序排序就是中序,然后就变成 105 题了
105,1008,449 基本一个题
效率
时间复杂度:
空间复杂度:
代码
/**
* 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* bstFromPreorder(vector<int>& pre) {
vector<int> in(pre.size());
copy(pre.begin(),pre.end(),in.begin());
sort(in.begin(),in.end());
return buildTree(pre,in);
}
TreeNode* buildTree(vector<int>& pre, vector<int>& in) {
return buildNode(pre,0,pre.size()-1,in,0,in.size()-1);
}
TreeNode* buildNode(vector<int>& pre,int pbegin,int pend, vector<int>& in,int ibegin,int iend) {
if (pbegin>pend||ibegin>iend||pend<0||iend<0){
return nullptr;
}
TreeNode *root=new TreeNode(pre[pbegin]);
int index;
for (int i=ibegin;i<iend;i++){
if (in[i]==pre[pbegin]){
index=i;
break;
}
}
root->left=buildNode(pre,pbegin+1,pbegin+index-ibegin,in,ibegin,index-1);
root->right=buildNode(pre,pbegin+index-ibegin+1,pend,in,index+1,iend);
return root;
}
};题意
从先序遍历还原二叉树
分析
类似于 291 题,只是那题先序遍历时,节点为空也记录在内,而这题不记录,而是使用节点的深度来代替。
因此,在前序遍历的时候,我们要带上深度,然后判断当前节点是不是当前深度的即可。
效率
时间复杂度:
空间复杂度:
代码
class Solution {
public:
TreeNode *recoverFromPreorder(string s) {
return get(s, 0);
}
TreeNode *get(string &s, int depth) {
if (s.empty()) {
return nullptr;
}
int i = 0;
while (i < s.size() && s[i] == '-') {
i++;
}
string n;
int j = i;
while (j < s.size() && s[j] != '-') {
n.push_back(s[j]);
j++;
}
if (i != depth) {
return nullptr;
}
s = s.substr(j, s.size() - j);
TreeNode *root = new TreeNode(stoi(n));
root->left = get(s, i+1);
root->right = get(s, i+1);
return root;
}
};序列化的题是指将二叉树转换为一定规则的字符串
list
题意
序列化和反序列化二叉树
分析
序列化:直接先序遍历,遇到 null 则写入 n 即可
反序列化:同样先序遍历,当前节点处,则取值创建节点,然后去掉该值,再同样创建左右孩子节点
效率
时间复杂度:
空间复杂度:
代码
class Codec {
public:
string serialize(TreeNode *root) {
if (!root) {
return "n";
}
string res;
res.append(to_string(root->val));
res.append(" ");
res.append(serialize(root->left));
res.append(" ");
res.append(serialize(root->right));
res.append(" ");
return res;
}
TreeNode *deserialize(string data) {
return get1(data);
}
TreeNode *get1(string &data) {
if (data.empty()) {
return nullptr;
}
if (data[0] == 'n') {
data.erase(data.begin());
while (*data.begin() == ' ') {
data.erase(data.begin());
}
return nullptr;
}
string t;
int i;
for (i = 0; i < data.size(); i++) {
if (data[i] == '-' || (data[i] >= '0' && data[i] <= '9')) {
t.push_back(data[i]);
continue;
}
break;
}
auto root = new TreeNode(stoi(t));
while (i < data.size() && data[i] == ' ') {
i++;
}
data = data.substr(i, data.size() - i);
root->left = get1(data);
root->right = get1(data);
return root;
}
};题意
序列化和反序列化 BST
分析
BST 也是普通的二叉树,故可以用 297 题的方法,直接序列化。但是这样没有利用到 BST 的有序性。
实际上,我们知道可以用前中后序中任意两个构造二叉树,而对于 BST,中序是一个有序的数列,即,随便给定前序或后序,只要排个序就是中序了。
故,我们只需序列化为前序或后序,反序列化时再排序,就可以构建了。
效率
时间复杂度:
空间复杂度:
代码
普通方式
class Codec {
public:
string serialize(TreeNode *root) {
if (!root) {
return "n";
}
string res;
res.append(to_string(root->val));
res.append(" ");
res.append(serialize(root->left));
res.append(" ");
res.append(serialize(root->right));
res.append(" ");
return res;
}
TreeNode *deserialize(string data) {
return get1(data);
}
TreeNode *get1(string &data) {
if (data.empty()) {
return nullptr;
}
if (data[0] == 'n') {
data.erase(data.begin());
while (*data.begin() == ' ') {
data.erase(data.begin());
}
return nullptr;
}
string t;
int i;
for (i = 0; i < data.size(); i++) {
if (data[i] == '-' || (data[i] >= '0' && data[i] <= '9')) {
t.push_back(data[i]);
continue;
}
break;
}
auto root = new TreeNode(stoi(t));
while (i < data.size() && data[i] == ' ') {
i++;
}
data = data.substr(i, data.size() - i);
root->left = get1(data);
root->right = get1(data);
return root;
}
};利用中序遍历有序性
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Codec {
public:
string serialize(TreeNode* root) {
string res=get(root);
// cout<<res<<endl;
return res;
}
string get(TreeNode* root) {
if (!root){
return "";
}
string res;
res.append(to_string(root->val));
res.append(" ");
res.append(get(root->left));
res.append(" ");
res.append(get(root->right));
res.append(" ");
return res;
}
TreeNode* deserialize(string data) {
vector<int> pre,in;
string s;
for (auto i:data){
if (i>='0'&&i<='9'){
s.push_back(i);
continue;
}
if (!s.empty()){
// cout<<s<<endl;
pre.push_back(stoi(s));
s.clear();
}
}
in.resize(pre.size());
copy(pre.begin(),pre.end(),in.begin());
sort(in.begin(),in.end());
return buildTree(pre,in);
}
TreeNode* buildTree(vector<int>& pre, vector<int>& in) {
return buildNode(pre,0,pre.size()-1,in,0,in.size()-1);
}
TreeNode* buildNode(vector<int>& pre,int pbegin,int pend, vector<int>& in,int ibegin,int iend) {
if (pbegin>pend||ibegin>iend||pend<0||iend<0){
return nullptr;
}
TreeNode *root=new TreeNode(pre[pbegin]);
int index;
for (int i=ibegin;i<iend;i++){
if (in[i]==pre[pbegin]){
index=i;
break;
}
}
root->left=buildNode(pre,pbegin+1,pbegin+index-ibegin,in,ibegin,index-1);
root->right=buildNode(pre,pbegin+index-ibegin+1,pend,in,index+1,iend);
return root;
}
};题意
在一个 m*n 的二维字符串数组中输出二叉树:
分析
坐标暂时不会算
效率
时间复杂度:
空间复杂度:
代码
class Solution {
vector<vector<string>> res;
public:
vector<vector<string>> printTree(TreeNode* root) {
int m=getHigh(root);
int n=(1<<m)-1;
while(m--){
vector<string> t(n);
res.push_back(t);
}
dfs(root,0,0,n);
return res;
}
void dfs(TreeNode *root,int row,int l,int r){
if (!root){
return;
}
int mid=(r-l)/2+l;
res[row][mid]=to_string(root->val);
dfs(root->left,row+1,l,mid-1);
dfs(root->right,row+1,mid+1,r);
}
int getHigh(TreeNode *root){
if (!root){
return 0;
}
return max(getHigh(root->left),getHigh(root->right))+1;
}
};题意
你需要采用前序遍历的方式,将一个二叉树转换成一个由括号和整数组成的字符串。
分析
这题重点在于读题
如果 root 左右子节点都不存在,则返回 root
如果 root 左右子节点都存在,则返回 root(left)(right)
如果 root 只有左节点存在,则返回 root(left)
如果 root 只有右节点存在,则返回 root()(right)
效率
时间复杂度:
空间复杂度:
代码
class Solution {
string res;
public:
string tree2str(TreeNode* root) {
pre(root);
return res;
}
void pre(TreeNode *root) {
if (!root){
return;
}
res.append(to_string(root->val));
if (!root->left && !root->right){
return;
}
res.append("(");
pre(root->left);
res.append(")");
if (!root->right){
return;
}
res.append("(");
pre(root->right);
res.append(")");
}
};二叉树的属性题,是指求二叉树中各个节点值等,比如求某层的节点值,求某处的叶子值等等
list
题意
给定一个二叉树,找出其最大深度。
分析
可以使用层序遍历,然后每层记录深度。也可以后序遍历,取左右子树深度的最大值,然后加本层 1即可。
效率
时间复杂度:
空间复杂度:
代码
class Solution {
public:
int maxDepth(TreeNode* root) {
if (!root){
return 0;
}
return max(maxDepth(root->left),maxDepth(root->right))+1;
}
};题意
给定一个 N 叉树,找到其最大深度。
分析
原理同 104,后序遍历,先求各子树高度,然后取最大值再加1
效率
时间复杂度:
空间复杂度:
代码
class Solution {
public:
int maxDepth(Node* root) {
if (!root){
return 0;
}
int res=0;
for (auto i:root->children){
res=max(res,maxDepth(i));
}
return res+1;
}
};题意
给定一个二叉树,找出其最小深度。
分析
原理同 104,先求各子树高度,然后取最小值再加1,但是,由于深度的定义,即到叶子节点的节点数,但是退化到链表后,深度就不是 1 了,因为根不是叶子节点,故,需要进行特判一下,排序链表这种情况。
效率
时间复杂度:
空间复杂度:
代码
class Solution {
public:
int minDepth(TreeNode* root) {
if (!root){
return 0;
}
int l=minDepth(root->left);
int r=minDepth(root->right);
if (l==0||r==0){
return max(l,r)+1;
}
return min(l,r)+1;
}
};题意
给定二叉树的根节点 root ,返回所有左叶子之和。
分析
这题关键点在于判断是不是左叶子,即如果到来叶子节点再判断,显然是不行的,因为叶子节点左右孩子都是空,所以要在左叶子父亲处判断。
这题可以前序也可以后序,后序要好理解一些,即先获取左右子树的个数,再判断当前是不是满足,最后加起来即可。
效率
时间复杂度:
空间复杂度:
代码
前序
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root) {
if (!root){
return 0;
}
if (!root->left){
return sumOfLeftLeaves(root->right);
}
int n=0;
if (!root->left->left&&!root->left->right){
n=root->left->val;
}
return n+sumOfLeftLeaves(root->left)+sumOfLeftLeaves(root->right);
}
};后序
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root) {
if (!root){
return 0;
}
if (!root->left){
return sumOfLeftLeaves(root->right);
}
int l=sumOfLeftLeaves(root->left);
int r=sumOfLeftLeaves(root->right);
int n=0;
if (!root->left->left&&!root->left->right){
n=root->left->val;
}
return n+l+r;
}
};题意
给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。
分析
最简单的自然是遍历一遍了,然后直接统计即可。但这样没有利用到完全二叉树这个条件。
所谓的完全二叉树,即叶子节点必然在同一层,且一定是最左边的。那么对于里面任意一个节点来说,它的左右子树一定一个是完全二叉树,一个是满二叉树。
假设满二叉树高度为 h,则节点总数为 ,故我们可以利用这一点,每次判断左右子树哪个是满二叉树,则这个子树就不用遍历了,直接计算即可,而另一个子树则同样的方式展开。
问题怎么判断哪个子树是满二叉树呢?
这个简单,判断左右子树最左边的高度是否相等即可,画画图就理解了。
效率
时间复杂度:
空间复杂度:
代码
普通遍历一遍
class Solution {
int res=0;
public:
int countNodes(TreeNode* root) {
if (!root){
return 0;
}
res++;
countNodes(root->left);
countNodes(root->right);
return res;
}
};完全二叉树性质
class Solution {
public:
int countNodes(TreeNode* root) {
if (!root){
return 0;
}
int l=getDepth(root->left);
int r=getDepth(root->right);
if (l==r){
return (1<<l)+countNodes(root->right);
}
return (1<<r)+countNodes(root->left);
}
int getDepth(TreeNode *root){
int level=0;
while(root){
root=root->left;
level++;
}
return level;
}
};题意
给定一棵二叉树的根节点 root ,请找出该二叉树中每一层的最大值。
分析
要得到每一层的最大值,就需要得到每一层的所有数据,则可以按层序来遍历,或者 dfs 时传深度,再用有序数据结构 set 来维护即可
优化
没必要用 set 来维护,直接存最值即可
效率
时间复杂度:
空间复杂度:
代码
层序遍历
class Solution {
public:
vector<int> largestValues(TreeNode* root) {
queue<TreeNode*> q;
vector<int> res;
q.push(root);
while(root && !q.empty()){
int size=q.size();
int m=INT_MIN;
while(size--){
auto t=q.front();
q.pop();
m=max(m,t->val);
if (t->left){
q.push(t->left);
}
if (t->right){
q.push(t->right);
}
}
res.push_back(m);
}
return res;
}
};dfs
class Solution {
map<int,set<int,greater<>>> m;
public:
vector<int> largestValues(TreeNode* root) {
vector<int> res;
dfs(root,0);
for (auto i:m){
res.push_back((*((i.second).begin())));
}
return res;
}
void dfs(TreeNode *root,int level){
if (!root){
return;
}
m[level].insert(root->val);
dfs(root->left,level+1);
dfs(root->right,level+1);
}
};直接存最值
class Solution {
map<int,int> m;
public:
vector<int> largestValues(TreeNode* root) {
vector<int> res;
dfs(root,0);
for (auto i:m){
res.push_back(i.second);
}
return res;
}
void dfs(TreeNode *root,int level){
if (!root){
return;
}
if (m.count(level)==0){
m[level]=INT_MIN;
}
m[level]=max(m[level],root->val);
dfs(root->left,level+1);
dfs(root->right,level+1);
}
};题意
给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。
分析
优化
也可以不分层次,从右往左遍历,最后一个就是答案
效率
时间复杂度:
空间复杂度:
代码
class Solution {
public:
int findBottomLeftValue(TreeNode* root) {
queue<TreeNode*> q;
q.push(root);
int res=root->val;
while(root && !q.empty()){
int size=q.size();
res=q.front()->val;
while(size--){
auto t=q.front();
q.pop();
if (t->left){
q.push(t->left);
}
if (t->right){
q.push(t->right);
}
}
}
return res;
}
};class Solution {
public:
int findBottomLeftValue(TreeNode* root) {
queue<TreeNode*> q;
q.push(root);
while(root && !q.empty()){
root=q.front();
q.pop();
if (root->right){
q.push(root->right);
}
if (root->left){
q.push(root->left);
}
}
return root->val;
}
};题意
求二叉树最大宽度
分析
首先要搞清楚宽度定义:每一层最左右两非 null 的节点,之间的节点数(之间 null 节点也要算)
由于宽度是在层上的定义,故可以用层序来做,对于每一层,要点就是找到最左右两非 null 的节点,问题在于怎么找。
层序当前队列中就是当前层中非 null 的节点个数,所以左右两节点还是好找,但是中间的节点数怎么算呢?中间 null 节点靠数个数是会出问题的。
所以,我们可以换一种计算方式。由于题目说,该数和完全二叉树结构相同,所以我们可以按完全二叉树给节点标坐标的方式,给每个节点标坐标,然后找到左右两节点后,直接相减即可。
当前坐标为 p,则左孩子为 2p,右孩子为 2p+1.
除了使用层序,dfs 的方式也能做,我们遍历时,只要找到当前这个节点,这一层中,最左边的节点坐标即可。
这需要两个信息:
故我们遍历时可以把层数带上,然后开一个哈希表记录层数 → 该层最左边的节点即可。那最左边的节点怎么求呢?
在 dfs 中,只要先访问左孩子再访问右孩子,那么该层访问的第一个节点必然是左节点。故判断 level 是否已经映射就能判断。
这题需要注意很坑的一点,就是坐标的范围问题,这题给定 case 有的很大,需要 unsigned long long 才能放得下。
效率
时间复杂度:
空间复杂度:
代码
bfs
class Solution {
public:
int widthOfBinaryTree(TreeNode* root) {
unsigned long long res=0;
queue<pair<TreeNode*,unsigned long long>> q;
q.push({root,0});
while(root&&!q.empty()){
unsigned long long l=0;
unsigned long long r=0;
int size=q.size();
for (int i=0;i<size;i++){
auto t=q.front();
q.pop();
TreeNode *n=t.first;
unsigned long long p=t.second;
if (i==0){
l=p;
}
if (i==size-1){
r=p;
}
if (n->left){
q.push({n->left,2*p});
}
if (n->right){
q.push({n->right,2*p+1});
}
}
res=max(res,r-l+1);
}
return res;
}
};dfs
class Solution {
// level -> leftPosition
unordered_map<int,unsigned long long> m;
unsigned long long res=0;
public:
int widthOfBinaryTree(TreeNode* root) {
dfs(root,0,0);
return res;
}
void dfs(TreeNode *root,int level,unsigned long long p) {
if (!root){
return;
}
if (m.count(level)==0){
m[level]=p;
}
res=max(res,p-m[level]+1);
dfs(root->left,level+1,2*p);
dfs(root->right,level+1,2*p+1);
}
};题意
给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
分析
要得到每一层最右边的数,最简单自然是层序遍历,直接取即可。也可以 dfs,不过需要考虑层数,故带上层数遍历,再开一个 map 从层序映射到值,每次更新,最后一次更新就是最右的值。
可以直接用 map 自动就维护 level 的顺序了,不用再排序.
效率
时间复杂度:
空间复杂度:
代码
bfs
class Solution {
public:
vector<int> rightSideView(TreeNode* root) {
queue<TreeNode*> q;
q.push(root);
vector<int> res;
while(root&&!q.empty()){
int size=q.size();
while(size--){
auto t=q.front();
q.pop();
if (size==0){
res.push_back(t->val);
}
if (t->left){
q.push(t->left);
}
if (t->right){
q.push(t->right);
}
}
}
return res;
}
};dfs
class Solution {
map<int,int> m;
public:
vector<int> rightSideView(TreeNode* root) {
vector<int> res;
pre(root,0);
for (auto i:m){
res.push_back(i.second);
}
return res;
}
void pre(TreeNode *root,int level){
if (!root){
return;
}
m[level]=root->val;
pre(root->left,level+1);
pre(root->right,level+1);
}
};题意
给定一个二叉树,找到该树中两个指定节点的最近公共祖先。
分析
如果是 BST,要求公共祖先,还可以利用大小来判断,但是如果是任意的二叉树,则不能这么做了。
最简单的,自然是开一个哈希表,把 p 到祖先节点路径全部标红,然后 q 再遍历到祖先节点,只要遇到红节点,那么自然这个节点就是相交节点。因此,我们需要遍历找到每个节点的父节点,再寻找即可。
而还有一种方法,即直接寻找,要寻找祖先节点,自然最好是从底往上找了,那么哪种遍历方式满足这一点呢?
显然是后序遍历。
那么什么时候,才会存在公共祖先节点呢?显然,是 p 和 q 分别在左右两个节点。
现在我们再来分析一下这个函数的作用
TreeNode *lowestCommonAncestor(TreeNode *root, TreeNode *p, TreeNode *q) {传入当前节点 root,和要寻找的两个节点 p,q 这好理解。那么返回值是什么?p 和 q 的公共祖先节点?
不,我们返回 root 子节点是否存在 p 和 q 节点。
如果 root 孩子节点存在 p,则返回 p,存在 q,则返回 q,如果都存在,那么就要看 p 和 q 是不是在一个子树上了。
上面的逻辑如下
if (!root){
return nullptr;
}
if (root==p){
return p;
}
if (root==q){
return q;
}
现在再来看左右子树的情况
auto l=lowestCommonAncestor(root->left,p,q);
auto r=lowestCommonAncestor(root->right,p,q);
如果左右子树都不存在 p 和 q,即 l 和 r 都为 nullptr,自然没有公共祖先。
if (!l&&!r){
return nullptr;
}
如果左子树为空,那么 p 或 q 就肯定在右子树
if (!l){
return r;
}
如果右子树为空,那么 p 或 q 就肯定在左子树
if (!r){
return l;
}
如果左右子树都不为空,则说明 p 和 q 一个在左一个在右,那么 root 自然就是公共祖先节点了
return root效率
时间复杂度:
空间复杂度:
代码
map
class Solution {
// node -> fatherNode
unordered_map<TreeNode *, TreeNode *> m;
unordered_map<TreeNode *, bool> visited;
public:
TreeNode *lowestCommonAncestor(TreeNode *root, TreeNode *p, TreeNode *q) {
m[root] = nullptr;
get(root);
while (p) {
visited[p] = true;
p = m[p];
}
while (q) {
if (visited[q]) {
return q;
}
q = m[q];
}
return nullptr;
};
void get(TreeNode *root) {
if (!root) {
return;
}
if (root->left) {
m[root->left] = root;
}
if (root->right) {
m[root->right] = root;
}
get(root->left);
get(root->right);
}
};后序遍历
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (!root){
return nullptr;
}
if (root==p){
return p;
}
if (root==q){
return q;
}
auto l=lowestCommonAncestor(root->left,p,q);
auto r=lowestCommonAncestor(root->right,p,q);
if (!l&&!r){
return nullptr;
}
if (!l){
return r;
}
if (!r){
return l;
}
return root;
}
};题意
给定一个非空二叉树的根节点 root , 以数组的形式返回每一层节点的平均值。与实际答案相差 10-5 以内的答案可以被接受。
分析
直接 dfs 或者 bfs,把每个元素放到对应层的和中,最后求平均值即可
效率
时间复杂度:
空间复杂度:
代码
class Solution {
map<int,double> m;
unordered_map<int,int> cnt;
public:
vector<double> averageOfLevels(TreeNode* root) {
vector<double> res;
pre(root,0);
for (auto i=m.begin();i!=m.end();i++){
res.push_back((*i).second/cnt[(*i).first]);
}
return res;
}
void pre(TreeNode *root,int level) {
if (!root){
return;
}
m[level]+=root->val;
cnt[level]++;
pre(root->left,level+1);
pre(root->right,level+1);
}
};题意
给你一个二叉树的根节点 root ,计算并返回 整个树 的坡度 。
分析
开全局变量记录结果,然后 dfs 遍历,对每个节点求坡度,再然后以这个节点为根的树的和即可
效率
时间复杂度:
空间复杂度:
代码
class Solution {
int res=0;
public:
int findTilt(TreeNode* root) {
pre(root);
return res;
}
int pre(TreeNode *root){
if (!root){
return 0;
}
int l=pre(root->left);
int r=pre(root->right);
res+=(abs(r-l));
return l+r+root->val;
}
};题意
给定一棵二叉树 root,返回所有重复的子树。
分析
对所有子树进行序列化,然后在哈希表中查找
效率
时间复杂度:
空间复杂度:
代码
class Solution {
unordered_map<string,int> m;
vector<TreeNode*> res;
public:
vector<TreeNode*> findDuplicateSubtrees(TreeNode* root) {
pre(root);
return res;
}
string pre(TreeNode *root){
if (!root){
return "#";
}
string s=to_string(root->val)+","+pre(root->left)+pre(root->right);
if (m[s]==1){
res.push_back(root);
}
m[s]++;
return s;
}
};二叉树验证题,主要是验证两个树的关系
list
题意
给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。
分析
要判断两颗树是否相同,自然方法就是遍历,然后对比每一个节点处是否相同。
可以 dfs,也可以 bfs
dfs 直接前序,先比较当前节点是否相同,只有两节点都存在且值相等,或都不存在才相同。然后左右子树都分别展开判断即可。
而 bfs 也很简单,直接开队列一层层的比较,比较每个不为空的节点即可,也可以放到一个队列中。需要注意的是,放入左右孩子和取出的顺序要一直,可以先放完一个节点的,也可以先放两个节点的左孩子。
效率
时间复杂度:
空间复杂度:
代码
class Solution {
public:
bool isSameTree(TreeNode* p, TreeNode* q) {
if (!p&&!q){
return true;
}
if (p&&!q){
return false;
}
if (!p&&q){
return false;
}
if (p->val!=q->val){
return false;
}
return isSameTree(p->left,q->left)&&isSameTree(p->right,q->right);
}
};class Solution {
public:
bool isSameTree(TreeNode *p, TreeNode *q) {
queue<TreeNode *> qu;
qu.push(p);
qu.push(q);
while (!qu.empty()) {
TreeNode *i = qu.front();
qu.pop();
TreeNode *j = qu.front();
qu.pop();
if (!i && !j) {
continue;
}
if (!j || !i || (i->val != j->val)) {
return false;
}
qu.push(i->left);
qu.push(j->left);
qu.push(i->right);
qu.push(j->right);
}
return true;
}
};题意
给你一个二叉树的根节点 root , 检查它是否轴对称。
分析
要检查是否轴对称,即检查左右子树是否对称,而这类似于 100,只是 100 是检查两树是否相等,而这是检查是否对称。
只需简单改动 100 题中比对的节点即可,即 p→left 和 q→right,p→right 和 q→left 相等。
可以 dfs,也可 bfs
效率
时间复杂度:
空间复杂度:
代码
dfs
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if (!root){
return true;
}
return isSameTree(root->left,root->right);
}
bool isSameTree(TreeNode* p, TreeNode* q) {
if (!p&&!q){
return true;
}
if (p&&!q){
return false;
}
if (!p&&q){
return false;
}
if (p->val!=q->val){
return false;
}
return isSameTree(p->left,q->right)&&isSameTree(p->right,q->left);
}
};bfs
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if (!root){
return true;
}
return isSameTree(root->left,root->right);
}
bool isSameTree(TreeNode *p, TreeNode *q) {
queue<TreeNode *> qu;
qu.push(p);
qu.push(q);
while (!qu.empty()) {
TreeNode *i = qu.front();
qu.pop();
TreeNode *j = qu.front();
qu.pop();
if (!i && !j) {
continue;
}
if (!j || !i || (i->val != j->val)) {
return false;
}
qu.push(i->left);
qu.push(j->right);
qu.push(i->right);
qu.push(j->left);
}
return true;
}
};题意
输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过 1,那么它就是一棵平衡二叉树。
分析
平衡二叉树即左右子树高度之差不超过 1 的树,那么这题就是 104 题求树高度的一个应用题,只需我们再递归求树高度时,拿到左右子树的高度,然后判断差的情况即可.
效率
时间复杂度:
空间复杂度:
代码
class Solution {
bool res=true;
public:
bool isBalanced(TreeNode* root) {
get(root);
return res;
}
int get(TreeNode *root){
if (!root){
return true;
}
int l=get(root->left);
int r=get(root->right);
if (abs(l-r)>1){
res=false;
}
return max(l,r)+1;
}
};题意
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
分析
由于 BST 的有序特性,故,我们只需利用这一点即可。而 BST 中序遍历后是升序的,所以这题本质上其实是考中序遍历。
效率
时间复杂度:
空间复杂度:
代码
递归
class Solution {
vector<int> nums;
public:
bool isValidBST(TreeNode* root) {
in(root);
for (int i=1;i<nums.size();i++){
if (nums[i-1]>=nums[i]){
return false;
}
}
return true;
}
void in(TreeNode *root){
if (!root){
return;
}
in(root->left);
nums.push_back(root->val);
in(root->right);
}
};迭代
class Solution {
public:
bool isValidBST(TreeNode* root) {
stack<TreeNode*> s;
long pre=LONG_MIN;
while(root || !s.empty()){
while(root){
s.push(root);
root=root->left;
}
root=s.top();
s.pop();
if (pre>=root->val){
return false;
}
pre=root->val;
root=root->right;
}
return true;
}
};题意
给定一个二叉树的 root ,确定它是否是一个 完全二叉树 。
分析
要验证是否是完全二叉树,得首先知道完全二叉树的性质,完全二叉树,即左右叶子节点都在最底层,且都在最左边。
换句话说,主要就是判断叶子节点是不是在一层,且是不是在最左边,如果是 dfs 的话,就比较麻烦,层序一层一层的遍历更加方便。
故直接层序遍历,然后对于每一层,如果出现了一次 null,则后面必须全为 null,不然就不是完全二叉树。
效率
时间复杂度:
空间复杂度:
代码
class Solution {
public:
bool isCompleteTree(TreeNode* root) {
queue<TreeNode*> q;
bool flag=false;
q.push(root);
while(root && !q.empty()){
int size=q.size();
for (int i=1;i<size+1;i++){
auto n=q.front();
q.pop();
if (!n){
flag=true;
continue;
}
if (flag){
return false;
}
q.push(n->left);
q.push(n->right);
}
}
return true;
}
};题意
判断 x 和 y 是否是堂兄弟
分析
所谓堂兄弟,就是指两节点在同一层,且父亲不同。故,我们要获取 x 和 y 的层数以及父节点。
可以 dfs,由于没有重复元素,直接根据值寻找即可,只是要带上层数。bfs 也行,这样就不用判断层数,只是要判断父节点是否相同,故可以带上节点的坐标。
效率
时间复杂度:
空间复杂度:
代码
dfs
class Solution {
int xdepth,ydepth,xparent,yparent;
public:
bool isCousins(TreeNode* root, int x, int y) {
xparent=-1;
yparent=-1;
get(root,0,x,y);
return (xdepth==ydepth)&&(xparent!=-1)&&(yparent!=-1)&&(xparent!=yparent);
}
void get(TreeNode *root,int level,int x,int y) {
if (!root){
return;
}
if ((root->left && root->left->val==x )||(root->right&&root->right->val==x)){
xparent=root->val;
xdepth=level;
}
if ((root->left && root->left->val==y )||(root->right&&root->right->val==y)){
yparent=root->val;
ydepth=level;
}
get(root->left,level+1,x,y);
get(root->right,level+1,x,y);
}
};bfs
class Solution {
public:
bool isCousins(TreeNode* root, int x, int y) {
queue<pair<TreeNode*,int>> q;
q.push({root,1});
while(root && !q.empty()){
int size=q.size();
bool xf=false;
int xp=-1;
bool yf=false;
int yp=-1;
while(size--){
auto t=q.front();
q.pop();
if (t.first->val==x){
xf=true;
xp=t.second/2;
}
if (t.first->val==y){
yf=true;
yp=t.second/2;
}
if (t.first->left){
q.push({t.first->left,2*t.second});
}
if (t.first->right){
q.push({t.first->right,2*t.second+1});
}
}
if (xf && yf && (xp!=-1) && (yp!=-1) && (xp!=yp)){
return true;
}
}
return false;
}
};题意
输入两棵二叉树 A 和 B,判断 B 是不是 A 的子结构
分析
B 如果是 A 的子结构,要么 A 、B 相等,要么 B 和 A 的左子树相等,要么和 A 的右子树相等。
而判断两树相等,类似于 100 题的做法,直接遍历判断每个节点即可,只是这题要多一点,如果当前节点 B 为空,那么 A 无论有没有节点,都是相同的。
效率
时间复杂度:
空间复杂度:
代码
class Solution {
public:
bool isSubStructure(TreeNode* a, TreeNode* b) {
if (!a || !b){
return false;
}
return equal(a,b) || isSubStructure(a->left,b) || isSubStructure(a->right,b);
}
bool equal(TreeNode *a,TreeNode *b) {
if (!b){
return true;
}
if (!a) {
return false;
}
if (a->val != b->val){
return false;
}
return equal(a->left,b->left) && equal(a->right, b->right);
}
};修改题就是涉及到树形状的修改,后序用得比较多
list
题意
给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。
分析
可以 dfs 或 bfs,基本原理就是,遍历节点,然后交换这个节点的左右孩子即可。
效率
时间复杂度:
空间复杂度:
代码
dfs
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (!root){
return nullptr;
}
auto t=root->left;
root->left=root->right;
root->right=t;
invertTree(root->left);
invertTree(root->right);
return root;
}
};bfs
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
queue<TreeNode*> q;
q.push(root);
while(root && !q.empty()){
int size=q.size();
while(size--){
auto t=q.front();
q.pop();
auto n=t->left;
t->left=t->right;
t->right=n;
if (t->left){
q.push(t->left);
}
if (t->right){
q.push(t->right);
}
}
}
return root;
}
};题意
合并两二叉树
分析
同时遍历两树相同位置的节点:
效率
时间复杂度:
空间复杂度:
代码
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if (!root1){
return root2;
}
if (!root2){
return root1;
}
root1->val+=root2->val;
root1->left=mergeTrees(root1->left,root2->left);
root1->right=mergeTrees(root1->right,root2->right);
return root1;
}
};题意
给你二叉树的根结点 root ,此外树的每个结点的值要么是 0 ,要么是 1 。
返回移除了所有不包含 1 的子树的原二叉树。
分析
由于要重建树,涉及修改节点,故最好的方式就是从底往上构建。而这是典型的后序遍历过程。
故后序遍历,先假设当前节点的左右子节点都构建好了,然后如果左右子节点不为空,说明左右子节点含有 1,故返回当前节点。
如果当前节点为 0,则返回空,否则还是返回 0
效率
时间复杂度:
空间复杂度:
代码
class Solution {
public:
TreeNode* pruneTree(TreeNode* root) {
if (!root){
return nullptr;
}
root->left=pruneTree(root->left);
root->right=pruneTree(root->right);
if (root->left || root->right){
return root;
}
if (root->val==0) {
return nullptr;
}
return root;
}
};题意
填充它的每个 next 指针
分析
最简单的,自然层序遍历即可,记录前指针,然后指向即可。
而这题由于是完全二叉树,所以,其实 next 指针只有两种情况:
故直接递归修改即可
效率
时间复杂度:
空间复杂度:
代码
class Solution {
public:
Node* connect(Node* root) {
if (!root) {
return nullptr;
}
if (root->left) {
root->left->next=root->right;
}
if (root->next && root->right){
root->right->next=root->next->left;
}
connect(root->left);
connect(root->right);
return root;
}
};题意
填充它的每个 next 指针
分析
这题比 116 要复杂一些,是任意给的二叉树,直接层序比较简单
效率
时间复杂度:
空间复杂度:
代码
class Solution {
public:
Node* connect(Node* root) {
queue<Node *> q;
q.push(root);
Node *pre=nullptr;
while(root && !q.empty()) {
int size=q.size();
pre=nullptr;
while(size--){
auto t=q.front();
q.pop();
if (pre){
pre->next=t;
}
pre=t;
if (t->left){
q.push(t->left);
}
if (t->right){
q.push(t->right);
}
}
}
return root;
}
};题意
往完全二叉树中插入节点
分析
对完全二叉树标号,最后叶子节点为 p,那么父节点为 p/2,而新节点就是该节点的子节点
故每次找到要插入的父节点即可
效率
时间复杂度:
空间复杂度:
代码
class CBTInserter {
TreeNode *head;
int p = -1;
int maxLevel = 0;
unordered_map<int, TreeNode *> m;
public:
CBTInserter(TreeNode *t) {
head = t;
}
int insert(int val) {
maxLevel = 0;
p = -1;
dfs(head, 0, 1);
if (p % 2 != 0) {
p++;
}
auto node = m[p / 2];
m.clear();
// cout << node->val << endl;
if (!node->left) {
node->left = new TreeNode(val);
return node->val;
}
node->right = new TreeNode(val);
return node->val;
}
TreeNode *get_root() {
return head;
}
void dfs(TreeNode *root, int level, int pos) {
if (!root) {
return;
}
if (level >= maxLevel) {
p = pos;
maxLevel = level;
}
m[pos] = root;
dfs(root->left, level + 1, 2 * pos);
dfs(root->right, level + 1, 2 * pos + 1);
}
};二叉树的路径题
list
题意
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。
分析
要看路径的和,那么直接 dfs 即可,需要注意,要判断的是 root 到叶子节点,所以必须到叶子节点的时候判断是否 targetSum 为 0.
也可以 bfs,每次往下一层走时,同时带上这个节点下面路径还需要的 targetSum,直到最后叶子节点判断是否为 0 即可。
效率
时间复杂度:
空间复杂度:
代码
dfs
class Solution {
public:
bool hasPathSum(TreeNode* root, int targetSum) {
if (!root){
return false;
}
if (!root->left && !root->right && targetSum==root->val){
return true;
}
auto l=hasPathSum(root->left,targetSum-root->val);
auto r=hasPathSum(root->right,targetSum-root->val);
return l||r;
}
};bfs
class Solution {
public:
bool hasPathSum(TreeNode* root, int targetSum) {
queue<pair<TreeNode*,int>> q;
q.push({root,targetSum});
while(root && !q.empty()){
int size=q.size();
while(size--){
auto t=q.front();
q.pop();
if (!t.first->left && !t.first->right && t.second-t.first->val==0){
return true;
}
if (t.first->left){
q.push({t.first->left,t.second-t.first->val});
}
if (t.first->right){
q.push({t.first->right,t.second-t.first->val});
}
}
}
return false;
}
};题意
给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
分析
原理同 112,只是 112 是判断存不存在,而这题则是找出所有满足的路径。
方法和 112 一样,可以 dfs 或 bfs。只是多出把路径存起来的过程。
对于 dfs,可以直接把路径信息带上,这样找到满足的叶子节点,路径信息也就找到了。
而对于 bfs,同样也可以带上路径,只是空间复杂度大了点,可以记录下每个节点的父节点,然后找到符合的叶子节点后,再回去找遍历路径即可。
效率
时间复杂度:
空间复杂度:
代码
class Solution {
vector<vector<int>> res;
public:
vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
dfs(root,targetSum,{});
return res;
}
void dfs(TreeNode *root,int targetSum,vector<int> num){
if (!root){
return;
}
num.push_back(root->val);
if (!root->left&&!root->right && targetSum-root->val==0){
res.push_back(num);
return;
}
dfs(root->left,targetSum-root->val,num);
dfs(root->right,targetSum-root->val,num);
}
};class Solution {
vector<vector<int>> res;
unordered_map<TreeNode *,TreeNode *> m;
public:
vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
queue<pair<TreeNode*,int>> q;
q.push({root,targetSum});
m[root]=nullptr;
while(root && !q.empty()){
int size=q.size();
while(size--){
auto t=q.front();
q.pop();
if (!t.first->left && !t.first->right && t.second-t.first->val==0){
get(t.first);
}
if (t.first->left){
m[t.first->left]=t.first;
q.push({t.first->left,t.second-t.first->val});
}
if (t.first->right){
m[t.first->right]=t.first;
q.push({t.first->right,t.second-t.first->val});
}
}
}
return res;
}
void get(TreeNode *root){
vector<int> t;
while(root){
t.push_back(root->val);
root=m[root];
}
res.push_back({t.rbegin(),t.rend()});
}
};题意
给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。
暴力
最简单的,自然是遍历每一个节点,然后再以此为根节点向下遍历,看是否存在该路径
class Solution {
int res=0;
public:
int pathSum(TreeNode* root, int targetSum) {
if (!root){
return 0;
}
dfs(root,targetSum);
pathSum(root->left,targetSum);
pathSum(root->right,targetSum);
return res;
}
bool dfs(TreeNode *root,int targetSum){
if (!root){
return false;
}
targetSum-=root->val;
if (targetSum==0){
res++;
}
return dfs(root->left,targetSum)|| dfs(root->right,targetSum);
}
};时间复杂度:,空间复杂度:
前缀和
class Solution {
int res=0;
int sum;
// presum->count
unordered_map<int,int> m;
public:
int pathSum(TreeNode* root, int targetSum) {
sum=targetSum;
m[0]=1;
pre(root,0);
return res;
}
void pre(TreeNode *root,int presum){
if (!root){
return;
}
int cur=presum+root->val;
res+=(m[cur-sum]);
m[cur]++;
pre(root->left,cur);
pre(root->right,cur);
m[cur]--;
}
};时间复杂度:,空间复杂度:
题意
给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。
分析
要求所有路径,直接 dfs 或 bfs 即可,只是要打印路径,所以遍历的过程中要带上之前的路径。
效率
时间复杂度:
空间复杂度:
代码
dfs
class Solution {
vector<string> res;
public:
vector<string> binaryTreePaths(TreeNode* root) {
pre(root,"");
return res;
}
void pre(TreeNode *root,string s) {
if (!root){
return;
}
s.append(to_string(root->val));
if (!root->left && !root->right){
res.push_back(s);
}
if (root->left || root->right){
s.append("->");
}
pre(root->left,s);
pre(root->right,s);
}
};bfs
class Solution {
public:
vector<string> binaryTreePaths(TreeNode* root) {
queue<pair<TreeNode*,string>> q;
q.push({root,""});
vector<string> res;
while(root && !q.empty()){
int size=q.size();
while(size--){
auto t=q.front();
q.pop();
t.second.append(to_string(t.first->val)+"->");
if (!t.first->left && !t.first->right){
t.second=t.second.substr(0,t.second.size()-2);
res.push_back(t.second);
}
if (t.first->left){
q.push({t.first->left,t.second});
}
if (t.first->right){
q.push({t.first->right,t.second});
}
}
}
return res;
}
};题意
给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
分析
要求最长路径,我们先来看路径该怎么求:
经过节点 a 的最长路径该是多少?显然,应该是左边的路径长度+右边的路径长度+1
而要求整个的最大路径长度,显然,我们开个全局变量,然后遍历节点,对每个节点都更新一下即可。
那么问题来了,对于节点 a,它的路径长度该怎么求呢?前面说了,需要知道左右子树的最大路径长度。
所以,我们定义一个函数,应该求该节点下方的最大路径长度
效率
时间复杂度:
空间复杂度:
代码
class Solution {
int res=0;
public:
int diameterOfBinaryTree(TreeNode* root) {
get(root);
return res;
}
// 求 root 下方的最大路径长度
int get(TreeNode *root){
if (!root){
return 0;
}
int l=get(root->left);
int r=get(root->right);
// 整体的最大
res=max(res,l+r);
// 经过 root 的下方最大路径长度
return max(l,r)+1;
}
};题意
给你一个二叉树的根节点 root ,返回其 最大路径和 。
分析
不同于 437,虽然两者都是求最大路径,但是从 437 对路径有限制,只是向下,所以可以用前缀和。而这题则对路径没有限制。
这题更类似于 543,都是求任意的最大路径,不过 543 只是求路径长度,而这题求的是所有路径上的节点和的最大值。
所以参考 543,全局开最大值,然后遍历每个节点进行更新。
遍历时,对每个节点进行操作,获取经过这个节点,下方的最大路径,然后再返回即可,需要注意的是,如果路径和小于,则不再选这个路径,所以要变成 0.
效率
时间复杂度:
空间复杂度:
代码
class Solution {
int res=INT_MIN;
public:
int maxPathSum(TreeNode *root) {
get(root);
return res;
}
// 求 root 下方的最大路径长度
int get(TreeNode *root) {
if (!root) {
return 0;
}
int l = get(root->left);
int r = get(root->right);
// 整体的最大
res = max(res, l + r + root->val);
// 经过 root 的下方最大路径长度
int t=max(l, r) + root->val;
if (t<0){
t=0;
}
return t;
}
};题意
给定一个二叉树的 root ,返回 最长的路径的长度 ,这个路径中的 每个节点具有相同值 。 这条路径可以经过也可以不经过根节点。
分析
本题相对于 543,只多了一步,那就是判断左右节点是否与当前节点值相同,如果不同,则左右节点路径长度要清零。
效率
时间复杂度:
空间复杂度:
代码
class Solution {
int res=0;
public:
int longestUnivaluePath(TreeNode* root) {
get(root);
return res;
}
// 求 root 下方的最大路径长度
int get(TreeNode *root){
if (!root){
return 0;
}
int l=get(root->left);
int r=get(root->right);
if (root->left && root->val != root->left->val){
l=0;
}
if (root->right && root->val != root->right->val){
r=0;
}
// 整体的最大
res=max(res,l+r);
// 经过 root 的下方最大路径长度
return max(l,r)+1;
}
};题意
计算从根节点到叶节点生成的 所有数字之和 。
分析
直接遍历即可,最后遍历到叶子节点再把和加到全局变量中,而由于涉及到前面的值,故遍历时需要把前面路径的和同样传递进来
效率
时间复杂度:
空间复杂度:
代码
dfs
class Solution {
int res=0;
public:
int sumNumbers(TreeNode* root) {
get(root,0);
return res;
}
void get(TreeNode *root,int sum){
if (!root){
return;
}
sum=sum*10+root->val;
if (!root->left && !root->right){
res+=sum;
}
get(root->left,sum);
get(root->right,sum);
}
};bfs
class Solution {
public:
int sumNumbers(TreeNode* root) {
int res=0;
queue<pair<TreeNode*,int>> q;
q.push({root,0});
while(root && !q.empty()){
int size=q.size();
while(size--){
auto t=q.front();
q.pop();
int n=t.second*10+t.first->val;
if (!t.first->left && !t.first->right){
res+=n;
}
if (t.first->left){
q.push({t.first->left,n});
}
if (t.first->right){
q.push({t.first->right,n});
}
}
}
return res;
}
};题意
给定一颗根结点为 root 的二叉树,树中的每一个结点都有一个 [0, 25] 范围内的值,分别代表字母 'a' 到 'z'。
返回 按字典序最小 的字符串,该字符串从这棵树的一个叶结点开始,到根结点结束。
分析
直接遍历,传递前面路径信息,到了叶子节点就放到有序集合中,最后取出来即可
效率
时间复杂度:
空间复杂度:
代码
class Solution {
set<string> m;
public:
string smallestFromLeaf(TreeNode* root) {
get(root,"");
return *m.begin();
}
void get(TreeNode *root,string s){
if (!root){
return;
}
s.push_back('a'+root->val);
if (!root->left && !root->right){
reverse(s.begin(),s.end());
m.insert(s);
}
get(root->left,s);
get(root->right,s);
}
};题意
给定一个二叉树(具有根结点 root), 一个目标结点 target ,和一个整数值 k 。
返回到目标结点 target 距离为 k 的所有结点的值的列表。 答案可以以 任何顺序 返回。
分析
效率
时间复杂度:
空间复杂度:
代码
class Solution {
unordered_map<int,TreeNode *> m;
int n;
vector<int> res;
public:
vector<int> distanceK(TreeNode* root, TreeNode* target, int k) {
m[root->val]=nullptr;
n=k;
dealParents(root);
search(target,nullptr,0);
return res;
}
void dealParents(TreeNode *root) {
if (!root){
return;
}
if (root->left){
m[root->left->val]=root;
dealParents(root->left);
}
if (root->right){
m[root->right->val]=root;
dealParents(root->right);
}
}
void search(TreeNode *root,TreeNode *pre,int depth) {
if (!root){
return;
}
if (depth==n){
res.push_back(root->val);
}
if (root->left!=pre) {
search(root->left,root,depth+1);
}
if (root->right!=pre) {
search(root->right,root,depth+1);
}
if (m[root->val]!=pre) {
search(m[root->val],root,depth+1);
}
}
};二叉搜索树相关的题,主要用中序
list
题意
在 BST 中查找值
分析
这题考的就是 BST 的性质,即 left<root<right,然后直接二分即可
效率
时间复杂度:
空间复杂度:
代码
递归
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
if (!root){
return nullptr;
}
if (root->val==val){
return root;
}
if (root->val<val){
return searchBST(root->right,val);
}
if (root->val>val){
return searchBST(root->left,val);
}
return nullptr;
}
};迭代
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
while(root){
if (root->val==val){
return root;
}
if (root->val<val){
root=root->right;
continue;
}
if (root->val>val){
root=root->left;
}
}
return nullptr;
}
};题意
给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。
分析
要把每个节点转换为所有比它大的值之和,那这些节点在什么地方?
根据 BST 的性质,即它的父节点和父节点的右子树所有节点,那么,那么如果我们有一种遍历方式,先遍历这些节点,再遍历该节点,不就能求了吗?
而这,其实就是中序反过来的循序,即 后中前的顺序。
效率
时间复杂度:
空间复杂度:
代码
class Solution {
int sum=0;
public:
TreeNode* convertBST(TreeNode* root) {
if (!root){
return nullptr;
}
convertBST(root->right);
sum+=root->val;
root->val=sum;
convertBST(root->left);
return root;
}
};题意
设计一个算法,找出二叉搜索树中指定节点的 “下一个” 节点(也即中序后继)。
分析
BST 中,左子树<root<右子树,而要求中序后继,即求下一个比当前节点大的元素。
如果当前节点在 left,那么下一个节点在根
如果当前节点在根,那么下一个节点在右子树
如果当前节点在右子树,那么下一个节点也在右子树
怎么判断目标节点 p 在哪里呢?很简单,和当前遍历的节点比较大小即可:
如果 p>root,说明 p 在 root 的右子树,自然 p 的下一个元素也在 root 的右子树
如果 p==root,自然 p 的下一个元素也在右子树
而如果 p<root,说明 p 在 root 的左子树,那么 p 的下一个元素可能在 root 的左子树,也可能就是 root 元素,需要先在左子树找,如果找不到就是 root
效率
时间复杂度:
空间复杂度:
代码
递归
class Solution {
public:
TreeNode* inorderSuccessor(TreeNode* root, TreeNode* p) {
if (!root){
return nullptr;
}
if (root->val <= p->val){
return inorderSuccessor(root->right,p);
}
auto n=inorderSuccessor(root->left,p);
if (n){
return n;
}
return root;
}
};迭代
class Solution {
public:
TreeNode* inorderSuccessor(TreeNode* root, TreeNode* p) {
TreeNode *res=nullptr;
while(root){
if (root->val<=p->val){
root=root->right;
continue;
}
res=root;
root=root->left;
}
return res;
}
};题意
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。
分析
后序遍历的结果是:左右根
BFS 的性质:左<根<右
所以,有效的数组就必然满足:最后一个元素是根,然后前面可以分为两个子数组,第一个小于根,第二个大于根。
不满足就不是 BST,然后递归即可
效率
时间复杂度:
空间复杂度:
代码
class Solution {
public:
bool verifyPostorder(vector<int>& post) {
if (post.empty()){
return true;
}
int root=post[post.size()-1];
int i=0;
int j;
while(i<post.size()-1 && post[i]<root){
i++;
}
j=i;
while(i<post.size()-1){
if (post[i]<root){
return false;
}
i++;
}
vector<int> l={post.begin(),post.begin()+i};
vector<int> r={post.begin()+i,post.end()-1};
return verifyPostorder(l)&&verifyPostorder(r);
}
};题意
给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。
分析
要在 BST 中获取差的最小值,显然需要两个数大小最接近,而 BST 的有序性质,中序的是否就是递增的,所以直接中序遍历,然后求相邻的差值最小即可
效率
时间复杂度:
空间复杂度:
代码
辅助数组
class Solution {
vector<int> nums;
public:
int getMinimumDifference(TreeNode* root) {
int res=INT_MAX;
in(root);
for (int i=1;i<nums.size();i++){
res=min(res,nums[i]-nums[i-1]);
}
return res;
}
void in(TreeNode *root){
if (!root){
return;
}
in(root->left);
nums.push_back(root->val);
in(root->right);
}
};前项指针
class Solution {
int pre=-999999;
int res=INT_MAX;
public:
int getMinimumDifference(TreeNode* root) {
in(root);
return res;
}
void in(TreeNode *root){
if (!root){
return;
}
in(root->left);
res=min(res,root->val-pre);
pre=root->val;
in(root->right);
}
};栈迭代
class Solution {
public:
int getMinimumDifference(TreeNode* root) {
stack<TreeNode *> s;
int res=INT_MAX;
int pre=-9999999;
while(root || !s.empty()){
while(root){
s.push(root);
root=root->left;
}
auto t=s.top();
s.pop();
res=min(res,t->val-pre);
pre=t->val;
root=t->right;
}
return res;
}
};题意
给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k ****个最小元素(从 1 开始计数)。
分析
直接中序遍历,然后计数 k 次即可
效率
时间复杂度:
空间复杂度:
代码
迭代
class Solution {
int res;
int cnt;
public:
int kthSmallest(TreeNode* root, int k) {
cnt=k;
dfs(root);
return res;
}
void dfs(TreeNode *root){
if (!root) {
return;
}
dfs(root->left);
cnt--;
if (cnt==0){
res=root->val;
return;
}
dfs(root->right);
}
};stack
class Solution {
public:
int kthSmallest(TreeNode* root, int k) {
stack<TreeNode*> s;
while(root || !s.empty()){
while(root){
s.push(root);
root=root->left;
}
auto t=s.top();
s.pop();
k--;
if (k==0){
return t->val;
}
root=t->right;
}
return 0;
}
};题意
给定一棵二叉搜索树,请找出其中第 k 大的节点的值。
分析
中序的逆序即可,就变成求第 k 小了,即 230 题
效率
时间复杂度:
空间复杂度:
代码
class Solution {
int res;
int cnt;
public:
int kthLargest(TreeNode* root, int k) {
cnt=k;
dfs(root);
return res;
}
void dfs(TreeNode *root){
if (!root) {
return;
}
dfs(root->right);
cnt--;
if (cnt==0){
res=root->val;
return;
}
dfs(root->left);
}
};题意
给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。
分析
对于 BST 的题,根据 BST 的性质,即中序是一个有序数组。那么基本上都可以转换成有序数组的题。
即对于这题,等价于:在有序数组中找众数
要怎么找?很简单,遍历一遍即可,相同的数都相邻,直接记录这个数的出现次数,比最大次数还大的,就是众数了。
效率
时间复杂度:
空间复杂度:
代码
class Solution {
vector<int> res;
int cnt=0;
int maxCnt=0;
int pre=INT_MIN;
int last=INT_MIN;
public:
vector<int> findMode(TreeNode* root) {
in(root);
return res;
}
void in(TreeNode *root){
if (!root){
return;
}
in(root->left);
if (pre==INT_MIN||root->val==pre){
cnt++;
} else {
cnt=1;
}
if (cnt==maxCnt && root->val!=last){
last=root->val;
res.push_back(root->val);
}
if (cnt>maxCnt){
res.clear();
res.push_back(root->val);
maxCnt=cnt;
last=root->val;
}
pre=root->val;
in(root->right);
}
};题意
给定一个二叉搜索树,找到该树中两个指定节点的最近公共祖先。
分析
根据二叉树的性质,如果 root 的值比 p 和 q 的值都大,说明 p 和 q都在左子树,如果 root 比 p 和 q 都小,则说明 p 和 q 都在右子树,否则这时 root 就是最近的公共祖先.
效率
时间复杂度:
空间复杂度:
代码
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (!root){
return nullptr;
}
if (root->val < p->val && root->val < q->val){
return lowestCommonAncestor(root->right,p,q);
}
if (root->val > p->val && root->val > q->val){
return lowestCommonAncestor(root->left,p,q);
}
return root;
}
};题意
给定一个二叉搜索树 root 和一个目标结果 k,如果 BST 中存在两个元素且它们的和等于给定的目标结果,则返回 true。
分析
直接开个哈希表,把之前遍历的元素记录起来,然后遍历到当前节点时,只要以前 k-val 元素存在,那么就满足
效率
时间复杂度:
空间复杂度:
代码
class Solution {
unordered_map<int,int> m;
public:
bool findTarget(TreeNode* root, int k) {
if (!root){
return false;
}
if (m.count(k-root->val)!=0){
return true;
}
m[root->val]++;
return findTarget(root->left,k) ||findTarget(root->right,k);
}
};题意
给定二叉搜索树(BST)的根节点 root 和要插入树中的值 value ,将值插入二叉搜索树。
分析
根据 BST 的性质,直接二叉寻找插入点即可
效率
时间复杂度:
空间复杂度:
代码
递归
class Solution {
int n;
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if (!root){
return new TreeNode(val);
}
n=val;
dfs(root);
return root;
}
void dfs(TreeNode *root) {
if (!root){
return;
}
if (n > root->val) {
if (!root->right){
root->right=new TreeNode(n);
return;
}
dfs(root->right);
return;
}
if (!root->left){
root->left=new TreeNode(n);
return;
}
dfs(root->left);
}
};迭代
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
if (!root){
return new TreeNode(val);
}
TreeNode *tmp=root;
while(root){
if (val > root->val) {
if (!root->right){
root->right=new TreeNode(val);
return tmp;
}
root=root->right;
continue;
}
if (!root->left){
root->left=new TreeNode(val);
return tmp;
}
root=root->left;
}
return tmp;
}
};题意
给你二叉搜索树的根节点 root ,同时给定最小边界 low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在 [low, high] 中。修剪树 不应该 改变保留在树中的元素的相对结构 (即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在 唯一的答案 。
分析
遍历 BST,分为这些情况:
如果当前节点 <low,说明左子树舍弃
如果当前节点 >high,说明右子树舍弃
修改左右子树
效率
时间复杂度:
空间复杂度:
代码
class Solution {
public:
TreeNode* trimBST(TreeNode* root, int low, int high) {
if (!root){
return nullptr;
}
if (root->val < low){
return trimBST(root->right,low,high);
}
if (root->val > high){
return trimBST(root->left,low,high);
}
root->left=trimBST(root->left,low,high);
root->right=trimBST(root->right,low,high);
return root;
}
};题意
给你二叉搜索树的根节点 root ,该树中的 恰好 两个节点的值被错误地交换。请在不改变其结构的情况下,恢复这棵树 。
分析
这题的难点就在于怎么找到这两个节点,由于 BST 的有序特性,也就等价于,在升序数组中,怎么找到两个被交换过的数。
方法就是记录前一个节点,然后和当前节点进行比较,如果更大,则说明有问题
效率
时间复杂度:
空间复杂度:
代码
class Solution {
TreeNode *pre;
TreeNode *t1;
TreeNode *t2;
public:
void recoverTree(TreeNode* root) {
in(root);
swap(t1->val,t2->val);
}
void in(TreeNode *root){
if (!root){
return;
}
in(root->left);
if (pre && pre->val > root->val){
if (!t1){
t1=pre;
}
t2=root;
}
pre=root;
in(root->right);
}
};题意
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点
分析
先找到要删除的节点:
效率
时间复杂度:
空间复杂度:
代码
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
if (!root){
return nullptr;
}
if (root->val > key){
root->left=deleteNode(root->left,key);
}
if (root->val < key){
root->right=deleteNode(root->right,key);
}
if (root->val != key){
return root;
}
if (!root->left && !root->right){
return nullptr;
}
if (!root->left){
return root->right;
}
if (!root->right){
return root->left;
}
TreeNode *cur=root->right;
while(cur->left){
cur=cur->left;
}
root->val=cur->val;
root->right=deleteNode(root->right,root->val);
return root;
}
};总的来说,二叉树的题并不难,多是中等题,并且主要依赖于各种遍历,只要遍历吃透了,那么二叉树的题就应该不是大问题。
Why Red Black Trees always having nil nodes as their leaf nodes and what is it implies?
How does a red-black tree work?