哈喽大家,这里是小饼干,面向就业编程,持续更新学习路径,欢迎关注点赞鸭~
这回整理了面试最常见的25道二叉树题,但在写这些题之前要会写最基本的二叉树遍历模板(递归和非递归),你会发现,这些题目都是在最基本的遍历基础上做了改进。
遍历不太会的小伙伴可以先看这个
点这里
不过我们通常采用递归的方法解决树的问题,这样写相较于非递归比较简洁。
自底向上——对应后序遍历 左右中 将上层值传给下层
自底向下——对应先序遍历 中左右 上层值依赖下层
差别在于是从子节点寻找答案还是从根节点寻找答案,哪个好找找哪个
1.Leetcode104.二叉树的最大深度
法一:自底向上
class Solution {
public:
int maxDepth(TreeNode* root) {
if(root==NULL)return 0;
int left=maxDepth(root->left);
int right=maxDepth(root->right);
return max(left,right)+1;
}
};
法二:自顶向下
class Solution {
public:
int ans=0;
int maxDepth(TreeNode* root) {
if(root==NULL)return 0;
search(root,0);
return ans;
}
void search(TreeNode *root,int depth){
if(root==NULL){
ans=max(ans,depth);
return;
}
search(root->left,depth+1);
search(root->right,depth+1);
}
};
2.Leetcode101. 对称二叉树
法一:递归
给定两个指针p,q,开始时都指向根结点,p左移时q右移,p右移时q左移
妙啊这解法
class Solution {
public:
bool isSymmetric(TreeNode* root) {
return check(root,root);
}
bool check(TreeNode* p,TreeNode*q){
if(!p&&!q)return true;
if(!p||!q)return false;
return p->val==q->val&&check(p->right,q->left)&&check(p->left,q->right);
}
};
法二:迭代
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if(root==NULL)return true;
queue<TreeNode*>q;
q.push(root);q.push(root);
while(!q.empty()){
auto l=q.front();q.pop();
auto r=q.front();q.pop();
if(!l&&!r)continue;
if((!l||!r)||(l->val!=r->val))return false;
q.push(l->left);
q.push(r->right);
q.push(l->right);
q.push(r->left);
}
return true;
}
};
3.Leetcode112. 路径总和
这里注意必须是根结点到叶子节点
叶子节点为左右孩子结点都为空的节点。
当根结点=目标值,且根结点一孩子节点为空另一孩子结点不为空时不满足条件,因为必须是根结点到叶子节点。根结点=叶子节点时满足条件
且根结点到一孩子节点为空另一孩子结点不为空的情况也必须排除。
法一:
自己写的,写了挺久,各种报错
感觉摸到点门道,把所有情况列出来就差不多了
class Solution {
public:
bool flag=false;
bool hasPathSum(TreeNode* root, int targetSum) {
if(root==NULL)return false;
if(root)check(root,targetSum);
return flag;
}
void check(TreeNode *root,int sum){
if(!root->left&&!root->right){
if(sum-root->val==0)flag=true;
return;
}
//一定要对是否为空进行判断,否则直接空指针异常
if(root->left)check(root->left,sum-root->val);
if(root->right)check(root->right,sum-root->val);
}
};
法二:
官方递归,看着比我的规整点,内容差不多
class Solution {
public:
bool hasPathSum(TreeNode *root, int sum) {
if (root == nullptr) {
return false;
}
if (root->left == nullptr && root->right == nullptr) {
return sum == root->val;
}
return hasPathSum(root->left, sum - root->val) ||
hasPathSum(root->right, sum - root->val);
}
};
4.Leetcode106. 从中序与后序遍历序列构造二叉树
class Solution {
public:
unordered_map<int,int>hash;
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
int l1=0,r1=inorder.size()-1;
int l2=0,r2=postorder.size()-1;
/*哈希表用来快速查找中序遍历中跟节点的位置*/
for(int i=0;i<=r1;i++){
hash[inorder[i]]=i;
}
return build(l1,r1,l2,r2,inorder,postorder);
}
TreeNode *build(int l1,int r1,int l2,int r2,vector<int> inorder, vector<int> postorder){
if(l1>r1)return NULL;
int midvalue=postorder[r2];
/*len用来记录左边序列长度*/
/*易写成根据坐标容易错误划分划分前序遍历中左子树*/
int len=hash[midvalue]-l1;
TreeNode *root=new TreeNode(midvalue);
TreeNode *left=build(l1,l1+len-1,l2,l2+len-1,inorder,postorder);
TreeNode *right=build(l1+len+1,r1,l2+len,r2-1,inorder,postorder);
root->left=left;
root->right=right;
return root;
}
};
参考了官解进行优化
class Solution {
public:
unordered_map<int,int>hash;
int post_idx;
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
int l=0,r=inorder.size()-1;
/*哈希表用来快速查找中序遍历中跟节点的位置*/
for(int i=0;i<=r1;i++){
hash[inorder[i]]=i;
}
post_idx=postorder.size()-1;
return build(l,r,inorder,postorder);
}
TreeNode *build(int l,int r,vector<int> inorder, vector<int> postorder){
if(l>r)return NULL;
int midvalue=postorder[post_idx];
/*len用来记录左边序列长度*/
/*易写成根据坐标容易错误划分划分前序遍历中左子树*/
int len=hash[midvalue]-l;
TreeNode *root=new TreeNode(midvalue);
post_idx--;
/*官解这里很妙,右子树先进行递归,其后序遍历序列末尾节点依次减一恰好对应其中间节点*/
TreeNode *right=build(l+len+1,r,inorder,postorder);
TreeNode *left=build(l,l+len-1,inorder,postorder);
root->left=left;
root->right=right;
return root;
}
};
5.Leetcode105. 从前序与中序遍历序列构造二叉树
class Solution {
public:
unordered_map<int,int>hash;
int pre_index;
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
int len=inorder.size();
for(int i=0;i<len;i++){
hash[inorder[i]]=i;
}
pre_index=0;
TreeNode *root=build(0,len-1,preorder,inorder);
return root;
}
TreeNode* build(int l,int r,vector<int>& preorder, vector<int>& inorder){
if(l>r)return NULL;
int midvalue=preorder[pre_index];
int len=hash[midvalue]-l;
TreeNode *root=new TreeNode(midvalue);
pre_index++;
TreeNode *left=build(l,l+len-1,preorder,inorder);
TreeNode *right=build(l+len+1,r,preorder,inorder);
root->left=left;
root->right=right;
return root;
}
};
6.Leetcode116. 填充每个节点的下一个右侧节点指针
给的是完美二叉树,直接广搜就行
class Solution {
public:
Node* connect(Node* root) {
if(root==NULL)return NULL;
queue<Node*>q;
q.push(root);
while(!q.empty()){
int len=q.size();
for(int i=0;i<len;i++){
auto t=q.front();
q.pop();
if(i!=len-1)t->next=q.front();
if(t->left)q.push(t->left);
if(t->right)q.push(t->right);
}
}
return root;
}
};
7.Leetcode117. 填充每个节点的下一个右侧节点指针 II
这次给的没说是完美二叉树了,但是不影响解题
原来6的位置为空时,5直接接7,上一题的代码也能过
优化解法:
队列的入队出队会带来一定的时间消耗
将每一层串成链表
class Solution {
public:
Node* connect(Node* root) {
if(root==NULL)return NULL;
Node *chead=new Node(0),*pcur,*ccur;
pcur=root;
ccur=chead;
while(true){
while(pcur){
if(pcur->left){
ccur->next=pcur->left;
ccur=ccur->next;
}
if(pcur->right){
ccur->next=pcur->right;
ccur=ccur->next;
}
pcur=pcur->next;
}
if(chead->next==NULL)break;
pcur=chead->next;
ccur=chead;
chead->next=NULL;
}
return root;
}
};
8.Leetcode236. 二叉树的最近公共祖先
求最近公共祖先,即自底向上求左右子树各包含p,q之一的根节点,或者一根结点为p/q,子树包含q/p一节点的根节点
自底向上对应后序遍历
class Solution {
public:
TreeNode *ans;
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
search(root,p,q);
return ans;
}
bool search(TreeNode* root, TreeNode* p, TreeNode* q){
if(root==NULL)return false;
bool getl=search(root->left,p,q);
bool getr=search(root->right,p,q);
if(getl&&getr||(getl||getr)&&(root==p||root==q)){
ans=root;
return true;
}
return root==p||root==q||getl||getr;
}
};
9.199. 二叉树的右视图
法一:直接bfs,记录每层最后一个节点
class Solution {
public:
vector<int>ans;
vector<int> rightSideView(TreeNode* root) {
if(!root)return ans;
queue<TreeNode*>q;
q.push(root);
while(!q.empty()){
int len=q.size();
for(int i=0;i<len;i++){
auto t=q.front();
if(i==len-1)ans.push_back(t->val);
q.pop();
if(t->left)q.push(t->left);
if(t->right)q.push(t->right);
}
}
return ans;
}
};
法二:按照一个中右左的遍历顺序进行遍历
class Solution {
public:
vector<int>ans;
int depth=0;
vector<int> rightSideView(TreeNode* root) {
search(root,0);
return ans;
}
void search(TreeNode *root,int depth){
if(root==NULL)return;
/*当ans.size()==depth,说明此节点时当前深度下被第一个访问到的节点,这里很妙*/
if(ans.size()==depth)ans.push_back(root->val);
depth++;
search(root->right,depth);
search(root->left,depth);
}
};
10.剑指 Offer 55 - II. 平衡二叉树
class Solution {
public:
bool ans=true;
bool isBalanced(TreeNode* root) {
dfs(root);
return ans;
}
int dfs(TreeNode *root){
if(root==NULL)return 0;
int left=dfs(root->left);
int right=dfs(root->right);
if(abs(left-right)>1){
ans=false;
return -1;
}
return max(left,right)+1;
}
};
11.剑指 Offer 27. 二叉树的镜像
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
TreeNode* mirrorTree(TreeNode* root) {
if(!root)return NULL;
TreeNode *newroot=new TreeNode(root->val);
copy(root,newroot);
return newroot;
}
void copy(TreeNode *root,TreeNode *newroot){
if(root==NULL)return;
if(root->right){
newroot->left=new TreeNode(root->right->val);
copy(root->right,newroot->left);
}
if(root->left){
newroot->right=new TreeNode(root->left->val);
copy(root->left,newroot->right);
}
return;
}
};
12.LCP 34. 二叉树染色
自己研究了一阵,发现只会递归算一条路径上的,整棵树的连续相邻子节点个数不超过k的个数就想不到怎么写了
看了下大佬的题解恍然大悟,自底向上,最重要的还是对各种情况的判断,
下一种情况又依赖于上一种情况,就可以用dp写
参考for_ak大佬的题解:
整个是一个后序遍历,自底向上
class Solution {
vector<int> dfs(TreeNode* root, int k) {
vector<int> f(k + 1, 0);
if (!root) return f;
auto l = dfs(root->left, k);
auto r = dfs(root->right, k);
/*当前节点不染色最大值=左子树任意情况+右子树任意情况下的最大值*/
f[0] = *max_element(l.begin(), l.end()) + *max_element(r.begin(), r.end());
for (int i = 1; i <= k; ++i) {
for (int j = 0; j < i; ++j) {
/*在当前节点不染色,和当前节点染色(左子树染色j个,右子树染色i-j-1个)*/
f[i] = max(f[i], root->val + l[j] + r[i - j - 1]);
}
}
return f;
}
public:
int maxValue(TreeNode* root, int k) {
auto f = dfs(root, k);
return *max_element(f.begin(), f.end());
}
};
13.剑指 Offer 36. 二叉搜索树与双向链表
法一:直接套了迭代的模板,成功AC
class Solution {
public:
Node* treeToDoublyList(Node* root) {
if(!root)return root;
Node *head=new Node(0),*pre=NULL,*cur=head;
stack<Node*>st;
Node *node=root;
while(node||!st.empty()){
while(node){
st.push(node);
node=node->left;
}
node=st.top();
st.pop();
cur->right=node;
cur->left=pre;
pre=cur;
cur=cur->right;
node=node->right;
}
cur->right=head->right;
cur->left=pre;
head->right->left=cur;
return head->right;
}
};
法二:递归
改一改中序遍历的递归就行
class Solution {
public:
Node *head=new Node(0),*cur=head,*pre=NULL;
Node* treeToDoublyList(Node* root) {
if(root==NULL)return root;
dfs(root);
cur->right=head->right;
cur->left=pre;
head->right->left=cur;
return head->right;
}
Node *dfs(Node *root){
if(root==NULL)return root;
dfs(root->left);
cur->right=root;
cur->left=pre;
pre=cur;
cur=cur->right;
dfs(root->right);
return root;
}
};
14.剑指 Offer 32 - III. 从上到下打印二叉树 III
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>>ans;
if(root==NULL)return ans;
queue<TreeNode*>q;
q.push(root);
bool flag=false;
while(!q.empty()){
vector<int>line;
int len=q.size();
for(int i=0;i<len;i++){
auto t=q.front();
q.pop();
line.push_back(t->val);
if(t->left)q.push(t->left);
if(t->right)q.push(t->right);
}
if(flag){
reverse(line.begin(),line.end());
}
flag=!flag;
ans.push_back(line);
}
return ans;
}
};
15.剑指 Offer 54. 二叉搜索树的第k大节点
class Solution {
public:
int num=0,ans;
int kthLargest(TreeNode* root, int k) {
search(root,k);
return ans;
}
void search(TreeNode*root,int k ){
if(!root)return;
search(root->right,k);
num++;
if(num==k){
ans=root->val;
return;
}
search(root->left,k);
}
};
16.剑指 Offer 33. 二叉搜索树的后序遍历序列
一个后序遍历序列可以划分为左子树的后序遍历序列(小于根结点)、右子树的后序遍历序列(大于根节点)、根结点
class Solution {
public:
bool verifyPostorder(vector<int>& postorder) {
if(postorder.size()==0)return true;
bool ans=search(postorder,0,postorder.size()-1);
return ans;
}
bool search(vector<int>postorder ,int l,int r){
if(l>=r)return true;
int m=l;
while(postorder[m]<postorder[r])m++;
int t=m;
while(postorder[t]>postorder[r])t++;
return t==r&&search(postorder,l,m-1)&&search(postorder,m,r-1);
}
};
17.剑指 Offer 26. 树的子结构
class Solution {
public:
bool isSubStructure(TreeNode* A, TreeNode* B) {
if(A==NULL||B==NULL)return false;
bool ans=search(A,B);
return ans||isSubStructure(A->left,B)||isSubStructure(A->right,B);
}
bool search(TreeNode *A,TreeNode *B){
if(!B)return true;
if(!A)return false;
return (A->val==B->val)&&search(A->left,B->left)&&search(A->right,B->right);
}
};
18.剑指 Offer II 047. 二叉树剪枝
class Solution {
public:
TreeNode* pruneTree(TreeNode* root) {
if(root==NULL)return NULL;
bool ans=search(root);
if(ans)return NULL;
return root;
}
bool search(TreeNode* root){
if(root==NULL)return true;
bool l=search(root->left);
bool r=search(root->right);
if(l)root->left=NULL;
if(r)root->right=NULL;
return root->val==0&&l&&r;
}
};
19.剑指 Offer II 052. 展平二叉搜索树
class Solution {
public:
TreeNode *head=new TreeNode(0),*cur=head;
TreeNode* increasingBST(TreeNode* root) {
if(root==NULL)return root;
dfs(root);
cur->right=NULL;
cur->left=NULL;
return head->right;
}
TreeNode *dfs(TreeNode* root){
if(root==NULL)return NULL;
dfs(root->left);
cur->right=root;
root->left=NULL;
cur=cur->right;
dfs(root->right);
return root;
}
};
20.剑指 Offer II 055. 二叉搜索树迭代器
法一:扁平化:中序遍历一遍,存到数组里,通过数组实现迭代器
class BSTIterator {
public:
vector<int>res;
int i;
void Inorder(TreeNode* root){
if(root==NULL)return;
Inorder(root->left);
res.push_back(root->val);
Inorder(root->right);
}
BSTIterator(TreeNode* root) {
Inorder(root);
i=0;
}
int next() {
return res[i++];
}
bool hasNext() {
if(i<res.size())return true;
return false;
}
};
法二:利用栈这一数据结构,通过迭代的方式对二叉树做中序遍历。无需预先计算出中序遍历的全部结果,只需要实时维护当前栈的情况即可。
class BSTIterator {
public:
stack<TreeNode *>st;
TreeNode *cur;
BSTIterator(TreeNode* root) {
cur=root;
}
int next() {
while(cur){
st.push(cur);
cur=cur->left;
}
cur=st.top();
st.pop();
int res=cur->val;
cur=cur->right;
return res;
}
bool hasNext() {
return (!st.empty()||cur);
}
};
21.剑指 Offer II 045. 二叉树最底层最左边的值
class Solution {
public:
int res=0,max=-1;
int findBottomLeftValue(TreeNode* root) {
find(root,0);
return res;
}
void find(TreeNode *root,int depth){
if(root==NULL)return;
find(root->left,depth+1);
/*上一句总能找到最左,在最左里找深度最大进行更新*/
if(depth>max){
max=depth;
res=root->val;
}
find(root->right,depth+1);
}
};
22.剑指 Offer II 056. 二叉搜索树中两个节点之和
法一:利用哈希表存值进行判断
class Solution {
public:
unordered_map<int,bool>hash;
bool findTarget(TreeNode* root, int k) {
if(root==NULL)return false;
stack<TreeNode*>st;
st.push(root);
TreeNode *cur=root;
while(!st.empty()||cur){
while(cur){
st.push(cur);
cur=cur->left;
}
cur=st.top();
st.pop();
hash[cur->val]=true;
if(hash[k-cur->val]&&k!=2*cur->val){
return true;
}
cur=cur->right;
}
return false;
}
};
法二:双指针法,利用二叉树平衡树的特点,左子树上的值都小于根结点,右子树上的值都大于根结点,设立两个指针left和right分别指向左子树和右子树上的节点
如果当前 left + right < k ,则 left 指向下一个节点值:left = 中序遍历迭代的下一个节点值;
如果 left + right > k,则 right 指向下一个节点值:right = 反中序遍历迭代的下一个节点值。
23.剑指 Offer II 053. 二叉搜索树中的中序后继
法一:常规中序遍历找
法二:按照二叉搜索树的性质
class Solution {
public:
TreeNode* inorderSuccessor(TreeNode* root, TreeNode* p) {
if(root==NULL)return NULL;
TreeNode* res=NULL;
TreeNode *cur=root;
while(cur){
if(cur->val>p->val){
res=cur;
cur=cur->left;
}else{
cur=cur->right;
}
}
return res;
}
};
24.面试题 04.02. 最小高度树
class Solution {
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
if(nums.empty())return NULL;
TreeNode *root=build(nums,0,nums.size());
return root;
}
TreeNode *build(vector<int>&nums,int l,int r){
if(l>=r)return NULL;
int mid=(l+r)/2;
TreeNode *root=new TreeNode(nums[mid]);
TreeNode *left=build(nums,l,mid);
TreeNode *right=build(nums,mid+1,r);
root->right=right;
root->left=left;
return root;
}
};
25.Leetcode297. 二叉树的序列化与反序列化||剑指 Offer 37. 序列化二叉树
class Codec {
public:
string str;
string serialize(TreeNode* root) {
if(root==NULL)return str;
Inorder(root);
return str;
}
void Inorder(TreeNode *root){
if(root==NULL){
str+="N,";
return;
}
/*前序遍历*/
str+=to_string(root->val)+",";
Inorder(root->left);
Inorder(root->right);
}
int i=0;
TreeNode* deserialize(string data) {
cout<<data;
if(data.length()==0)return NULL;
TreeNode *root=ReInorser(data);
return root;
}
TreeNode* ReInorser(string data){
if(data[i]=='N'){
i+=2;
return NULL;
}
TreeNode *root;
int num=0;
bool flag=false;
if(data[i]=='-'){
i++;
flag=true;
}
while(data[i]!=','){
num=num*10+data[i++]-'0';
}
i++;
root=new TreeNode(flag?-num:num);
TreeNode *left=ReInorser(data);
TreeNode *right=ReInorser(data);
root->left=left;
root->right=right;
return root;
}
};