1 完全二叉树的节点个数
①遍历法
无需多说,利用左右子树的个数之和+1即可,标准的后序遍历思路。当然也能用层序遍历,但不必多写。
class Solution {
public:
int countNodes(TreeNode* root) {
if(root==nullptr)return 0;
return 1+countNodes(root->left)+countNodes(root->right);
}
};
②满二叉树
显然,完全二叉树是由若干个满二叉树组成的,而满二叉树的个数可以简单的使用2^depth-1进行计算。
因此最简单的思路是判断最左节点和最右节点的深度是否一致,一致说明为满二叉树,不一致则递归左右子树。
class Solution {
public:
int countNodes(TreeNode* root) {
if(root==nullptr)return 0;
int left_depth=0;
int right_depth=0;
TreeNode* cur=root;
while(cur->left)
{
cur=cur->left;
++left_depth;
}
cur=root;
while(cur->right)
{
cur=cur->right;
++right_depth;
}
if(left_depth==right_depth)return (2<<(left_depth))-1;
else return 1+countNodes(root->left)+countNodes(root->right);
}
};
2 平衡二叉树
依然是后序遍历的思路,判断左右子树的高度是否相差超过1,利用-1作为标记可以从底层进行判断。
class Solution {
public:
int isBalancedAssist(TreeNode* root)
{
//终止条件
if(root==nullptr)return 0;
if(root->left==nullptr && root->right==nullptr)return 1;
int left_result=isBalancedAssist(root->left);
int right_result=isBalancedAssist(root->right);
if(left_result==-1 || right_result==-1 || left_result-right_result>1 || right_result-left_result>1)
return -1;
else return 1+max(left_result,right_result);
}
bool isBalanced(TreeNode* root) {
if(root==nullptr)return true;
return isBalancedAssist(root)!=-1;
}
};
3 二叉树的所有路径
依然是后序遍历的思路,路径即根节点->左子树路径+根节点->右子树路径,理论上利用栈和回溯也可以实现。
class Solution {
public:
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> result;
vector<string> left_result;
vector<string> right_result;
if(root==nullptr)return result;
if(root->left==nullptr&&root->right==nullptr)
result.push_back(to_string(root->val));
else
{
if(root->left)
left_result=binaryTreePaths(root->left);
if(root->right)
right_result=binaryTreePaths(root->right);
for(int i=0;i<left_result.size();i++)
{
result.push_back(to_string(root->val)+"->"+left_result[i]);
}
for(int i=0;i<right_result.size();i++)
{
result.push_back(to_string(root->val)+"->"+right_result[i]);
}
}
return result;
}
};
4 左叶子之和
层序遍历或后序遍历都可以处理,给出后序遍历处理代码。
class Solution {
public:
int sumOfLeftLeavesAssist(TreeNode* root,bool isLeft)
{
if(root==nullptr)return 0;
//左叶子
if(root->left==nullptr && root->right==nullptr && isLeft)
return root->val;
//左子树左叶子之和
int left_sum=sumOfLeftLeavesAssist(root->left,true);
//右子树左叶子之和
int right_sum=sumOfLeftLeavesAssist(root->right,false);
//后序处理
return left_sum+right_sum;
}
int sumOfLeftLeaves(TreeNode* root) {
return sumOfLeftLeavesAssist(root,false);
}
};
5 找树左下角的值
层序遍历是最容易想到的,这里采用递归+回溯进行处理。
class Solution {
public:
int maxDepth=-1;
int result;
void traversal(TreeNode* root,int depth)
{
if(root->left==nullptr && root->right==nullptr)
{
result = depth > maxDepth ? root->val : result;
maxDepth = depth > maxDepth ? depth : maxDepth;
}
if(root->left)
{
++depth;
traversal(root->left , depth);
--depth;
}
if(root->right)
{
++depth;
traversal(root->right,depth);
depth--;
}
}
int findBottomLeftValue(TreeNode* root) {
//搜到最深处的最左节点
//递归+回溯思想
TreeNode* cur=root;
traversal(root,0);
return result;
}
};
6 路径总和
这题最简单明了的思路必然是递归,将target进行缩小即可。
class Solution {
public:
bool hasPathSum(TreeNode* root, int targetSum) {
//特殊情况处理
if(root==nullptr)return false;
//递归终止条件
if(root->left==nullptr && root->right==nullptr && root->val==targetSum)return true;
if(root->left==nullptr && root->right==nullptr && root->val!=targetSum)return false;
if(root->left==nullptr && root->right)
return hasPathSum(root->right,targetSum-root->val);
if(root->left && root->right==nullptr)
return hasPathSum(root->left,targetSum-root->val);
return hasPathSum(root->left,targetSum-root->val)||hasPathSum(root->right,targetSum-root->val);
}
};
7 从中序与后序遍历序列构造二叉树
中序遍历+(前序遍历/后序遍历)可以确定一颗元素不重复的二叉树,因为任何遍历左子树元素必然在右子树元素之前,而根节点必然在后序遍历最后或前序遍历最前,因此可以利用根节点和中序遍历,确定左右子树的元素,从而迭代构建二叉树。
以下两种分别代表着容易理解和节约空间的两种实现,即对于子树的遍历结果的管理方式不同。
class Solution {
public:
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
//后序遍历的最后一个一定是根节点
//在中序遍历中找到根节点,其左侧为左子树元素,其右侧为右子树元素
//后序遍历中也一样,左子树元素在先,右子树元素在后,跟着中序遍历进行寻找即可
//递归进行build,直到遇到叶节点结束
//建立根节点
int root_val=postorder[postorder.size()-1];
TreeNode* root=new TreeNode(root_val);
//递归终止条件
if(inorder.size()==1)return root;
//判断左右子树的标志
bool left=true;
//存放左子树和右子树的中序与后序遍历
vector<int> left_in,left_post,right_in,right_post;
//循环存放遍历结果
for(int i=0;i<inorder.size();++i)
{
if(inorder[i]==root_val)
{
//接下来都是右子树
left=false;
continue;
}
//左子树元素
if(left)
{
left_in.push_back(inorder[i]);
left_post.push_back(postorder[i]);
}
//右子树元素
else
{
right_in.push_back(inorder[i]);
right_post.push_back(postorder[i-1]);
}
}
//左右节点连接
if(left_in.size()!=0)root->left=buildTree(left_in,left_post);
if(right_in.size()!=0)root->right=buildTree(right_in,right_post);
return root;
}
};
class Solution {
public:
TreeNode* buildTreeAssist(vector<int>& inorder, vector<int>& postorder,int in_left,int in_right,
int post_left,int post_right)
{
int root_val=postorder[post_right];
TreeNode* root=new TreeNode(root_val);
if(in_left==in_right)return root;
int new_in_mid,new_post_mid;
for(int i=0;i<=in_right-in_left;i++)
{
if(inorder[in_left+i]==root_val)
{
new_in_mid=in_left+i-1;
new_post_mid=post_left+i-1;
break;
}
}
if(new_in_mid>=in_left)
root->left=buildTreeAssist(inorder,postorder,in_left,new_in_mid,post_left,new_post_mid);
if(new_in_mid+2<=in_right)
root->right=buildTreeAssist(inorder,postorder,new_in_mid+2,in_right,new_post_mid+1,post_right-1);
return root;
}
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
return buildTreeAssist(inorder,postorder,0,inorder.size()-1,0,postorder.size()-1);
}
};
8 最大二叉树
和上一道中序遍历+后序遍历构建二叉树没啥本质区别,唯一的区别在于根节点的确定使用最大值,而不是由后序遍历来确定。
class Solution {
public:
int max(vector<int>& nums)
{
int max_val=nums[0];
for(int i=1;i<nums.size();i++)
{
if(nums[i]>max_val)max_val=nums[i];
}
return max_val;
}
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
int root_val=max(nums);
TreeNode* root=new TreeNode(root_val);
if(nums.size()==1)
return root;
vector<int> left_nums,right_nums;
bool left=true;
for(int i=0;i<nums.size();i++)
{
if(nums[i]==root_val)
{
left=false;
continue;
}
if(left)left_nums.push_back(nums[i]);
else right_nums.push_back(nums[i]);
}
if(left_nums.size())root->left=constructMaximumBinaryTree(left_nums);
if(right_nums.size())root->right=constructMaximumBinaryTree(right_nums);
return root;
}
};
9 合并二叉树
递归法是最简单明了的,利用前序遍历思想,对根节点处理完后,对左右子树进行递归。
另外采用队列进行层序遍历也同样可以完成,只是需要注意节点赋值和条件判断之间的顺序不能随意打乱。
①递归(前序遍历)
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
//递归终止
if(root1==nullptr)return root2;
if(root2==nullptr)return root1;
//均非空
//前序遍历
root1->val=root1->val+root2->val;
root1->left=mergeTrees(root1->left,root2->left);
root1->right=mergeTrees(root1->right,root2->right);
return root1;
}
};
①队列(层序遍历)
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if(root1==nullptr)return root2;
if(root2==nullptr)return root1;
queue<TreeNode*> que;
que.push(root1);
que.push(root2);
TreeNode* cur1;
TreeNode* cur2;
while(!que.empty())
{
cur1=que.front();
que.pop();
cur2=que.front();
que.pop();
//中
cur1->val+=cur2->val;
//左
if(cur1->left && cur2->left)
{
que.push(cur1->left);
que.push(cur2->left);
}
else {if(cur1->left==nullptr)cur1->left=cur2->left;}
//右
if(cur1->right && cur2->right)
{
que.push(cur1->right);
que.push(cur2->right);
}
else {if(cur1->right==nullptr)cur1->right=cur2->right;}
}
return root1;
}
};
10 总结
普通二叉树的题目已经基本上做完了,找到规律实际上部分容易。绝大数情况下,DFS的前中后序遍历+递归是最好且最简单的实现思路,而迭代法与回溯也在做题中慢慢找到一些感觉了。
还是选择屈服算了,不再对每一个段落进行缩进,直接进行一个烂的摆,MarkDown成功驯化我了。另外马上就要开学了,接下来应该是对二叉搜索树、AVL树、B树以及最小顶堆进行一些复习和练习。
在学业方面,打算在GAN、Diffusion以及NERF方面进行一些钻研吧。
——2023.2.19