剑指offer-树
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字
剑指 07.重建二叉树
class Solution {
private:
unordered_map<int, int> index;
public:
TreeNode* myBuildTree(const vector<int>& preorder, const vector<int>& inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) {
if (preorder_left > preorder_right) {
return nullptr;
}
// 前序遍历中的第一个节点就是根节点
int preorder_root = preorder_left;
// 在中序遍历中定位根节点
int inorder_root = index[preorder[preorder_root]];
// 先把根节点建立出来
TreeNode* root = new TreeNode(preorder[preorder_root]);
// 得到左子树中的节点数目
int size_left_subtree = inorder_root - inorder_left;
// 递归地构造左子树,并连接到根节点
// 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
root->left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
// 递归地构造右子树,并连接到根节点
// 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
root->right = myBuildTree(preorder, inorder, preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right);
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
int n = preorder.size();
// 构造哈希映射,帮助我们快速定位根节点
for (int i = 0; i < n; ++i) {
index[inorder[i]] = i;
}
return myBuildTree(preorder, inorder, 0, n - 1, 0, n - 1);
}
};
题解来源:官方https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/solution/mian-shi-ti-07-zhong-jian-er-cha-shu-by-leetcode-s/
剑指 Offer 26. 树的子结构
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。
/**
* 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:
bool recure(TreeNode* A,TreeNode* B){
if(B == nullptr) return true;// 表示B已经遍历完
if(A == nullptr || B->val != A->val) return false;// 判断A是否遍历完,或当前A的
值 是否对于B的值
return recure(A->left,B->left) && recure(A->right,B->right);
}
bool isSubStructure(TreeNode* A, TreeNode* B) {
if(A == nullptr || B == nullptr) return false;
return recure(A,B) || isSubStructure(A->left,B) || isSubStructure(A->right,B);
}
};
剑指 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) {}
* };
*/
// DFS
class Solution {
public:
TreeNode* mirrorTree(TreeNode* root) {
if(!root) return NULL; // 检查数据有效性 & 退出递归条件
TreeNode* tem_right=root->right;// 保存右子结点
root->right=mirrorTree(root->left);// 递归左右子结点,并交换左右结点
root->left=mirrorTree(tem_right);
return root;// 返回当前访问的结点 && 最终返回跟结点
}
};
// BFS
class Solution {
public:
TreeNode* mirrorTree(TreeNode* root) {
if(root == nullptr) return nullptr;
stack<TreeNode*> stack;
stack.push(root);
while (!stack.empty())
{
TreeNode* node = stack.top();
stack.pop();
if (node->left != nullptr) stack.push(node->left);
if (node->right != nullptr) stack.push(node->right);
TreeNode* tmp = node->left;
node->left = node->right;
node->right = tmp;
}
return root;
}
};
剑指 Offer 28. 对称的二叉树
请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
/**
* 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:
bool isSymmetric(TreeNode* root) {
if(!root) return true;
return recur(root->left,root->right);
}
bool recur(TreeNode* L,TreeNode* R)
{
if(L==NULL && R==NULL) return true;
if(L==NULL || R==NULL || L->val!=R->val) return false;
return recur(L->left,R->right) && recur(L->right,R->left);
}
};
题解来自于:面试题28. 对称的二叉树(递归,清晰图解)
剑指 Offer 32 - II. 从上到下打印二叉树 II
从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。
/**
* 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:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> ans;
queue<TreeNode*> que;
if(root) que.push(root);
// vector<int> same_lay;
while(!que.empty())
{
vector<int> same_lay;
int size = que.size();
for(int i=0;i<size;i++)// 这里的size是每层结点的个数,不能用que.size(),因为que在这个循环中会变化
{
TreeNode* node = que.front();
que.pop();//每一层有多少个节点 就会被弹多少次 与此同时压入下一层的左右子节点
same_lay.push_back(node->val);
// same_lay.push_back(node->val);
// que.pop();//
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
ans.push_back(same_lay);
}
return ans;
}
};
剑指 Offer 37.序列化二叉树
请实现两个函数,分别用来序列化和反序列化二叉树。
/**
* 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:
// Encodes a tree to a single string.
string res;
string serialize(TreeNode* root) { //序列化阶段
dfs(root);
return res;
}
void dfs(TreeNode* root) //用前序遍历的方式序列化二叉树
{
if (!root) res += "NULL,"; //空节点存入NULL字符串
else
{
res += to_string(root->val) + ','; //非空节点存入字符串数值
dfs(root->left); //这里也可以用中序或后序
dfs(root->right); //只要保证反序列化时也用同样的方式即可
}
}
// Decodes your encoded data to tree.
queue<string> q;
TreeNode* deserialize(string data) { //反序列化阶段
int i = 0, j = 0; //用双指针按照逗号来分割字符串
while (i < data.size()) //以逗号分割字符串,存入队列q中
{
while (data[i] != ',' && i < data.size()) i ++ ;
string tmp = data.substr(j, i - j);
q.push(tmp);
i ++ ;
j = i;
}
return de_dfs(); //递归建树
}
TreeNode* de_dfs()
{
auto t = q.front();
q.pop();
if (t == "NULL") return NULL;
TreeNode* root = new TreeNode(stoi(t)); //仍然用前序遍历的方式构建二叉树
root->left = de_dfs();
root->right = de_dfs();
return root;
}
};
// Your Codec object will be instantiated and called as such:
// Codec codec;
// codec.deserialize(codec.serialize(root));
1、序列化:遍历二叉树,将每个节点值保存到string中,空节点的位置存NULL。
2、反序列化:将string中的value保存到deque中,再对deque重建二叉树。
注意:
1、val和NULL后面都有","来分割字符串;
2、保存string时,用两个指针按照逗号分割字符串。
题解来自于:https://leetcode-cn.com/problems/xu-lie-hua-er-cha-shu-lcof/solution/jian-zhi-offer-37-xu-lie-hua-er-cha-shu-rcu0v/
剑指 Offer 44. 数字序列中某一位的数字
数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。
请写一个函数,求任意第n位对应的数字。
分析
优雅三步法。这道题其实就是找一下规律,同时需要我们细心计算好一些细节,比如位数、余数、索引等。其实不算难,且看分析。
第一步,我们得找到 n 属于哪个数位里的索引。比如 n = 5,那 n 就是个位这个数位里的索引;或者 n = 11,那 n 就是十位这个数位里的索引。
第二步,确定了 n 属于哪个数位,我们需要进一步定位到 n 具体属于哪个数。比如 n = 11,指的就是 10 这个数。
第三步,确定了 n 属于哪个数,我们就需要算出 n 是这个数的第几位,从而得到最终答案。比如 n = 11,指的是 10 这个数的第 1 位(索引从 0 开始),从而最终答案就是 0。
这里我们定义了几个变量来帮助我们完成上面的三步(代码中对应处也有注释):
digit。表示数位,比如个位,digit = 1;十位,digit = 2;百位,digit = 3;以此类推。
start。表示该数位的所有数的起始点数。比如个位,start = 1(0 做特例处理,不算在内);十位,start = 10;千位,start = 1000;以此类推。
index_count。表示该数位一共有的索引个数。比如个位,index_count = 9(1-9);十位,index_count = 180(10-99);百位,index_count = 2700(100-999);以此类推。
总结规律:index_count = digit * 9 * start。比如十位,index_count = 2 * 9 * 10 = 180。
class Solution {
public:
int findNthDigit(int n) {
if(n == 0) {return 0;}
int digit = 1; // 数位(个位/十位/百位/...,就是1/2/3/...)
long start = 1; // 属于该数位的所有数的起始点数(个位是1,十位是10,百位是100)
long index_count = digit * 9 * start; // 该数位的数一共的索引个数(不是数字个数)
while(n > index_count ) {
// 找出 n 属于那个数位里的索引
n -= index_count;
++ digit;
start *= 10;
index_count = digit * 9 * start;
}
// 上面的循环结束后:
// digit 等于原始的 n 所属的数位;start 等于原始的 n 所属数位的数的起始点
// index_count 等于原始的 n 所属数位的索引总个数(不重要了,下面不用)
// n 等于在当前数位里的第 n - 1 个索引(索引从 0 开始算起)
long num = start + (n - 1) / digit; // 算出原始的 n 到底对应哪个数字
int remainder = (n - 1) % digit; // 余数就是原始的 n 是这个数字中的第几位
string s_num = to_string(num); // 将该数字转为 string 类型
return int(s_num[remainder] - '0'); // n 对应着第 remainder 位,再转成 int
}
};
题解来自 力扣(LeetCode)
作者:superkakayong
链接:https://leetcode-cn.com/problems/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof/solution/zi-jie-ti-ku-jian-44-zhong-deng-shu-zi-xu-lie-zhon/
剑指 Offer 54. 二叉搜索树的第k大节点
给定一棵二叉搜索树,请找出其中第k大的节点。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
// 中序遍历是递增序列,反向中序遍历则位递减序列,找到第k个元素,即为第k大的节点
class Solution {
public:
int count = 0, res = 0;
int kthLargest(TreeNode* root, int k) {
inorder(root, k);
return res;
}
void inorder(TreeNode* root, int k) {
if (!root) return;
inorder(root -> right, k); // 右
++ count;
if (count == k)
{
res = root -> val;
return;
}
inorder(root -> left, k); // 左
}
};
剑指 Offer 55 - I. 二叉树的深度
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
// 递归,当前字树的最大深度 = 其左子树和右子树最大深度的最大值 + 1
class Solution {
public:
int maxDepth(TreeNode* root) {
if (!root) return 0;
int left=maxDepth(root->left);
int right=maxDepth(root->right);
return max(left,right)+1;
}
};
// 层次遍历,记录层数
class Solution {
public:
int maxDepth(TreeNode* root) {
if(root == NULL) return 0;
queue<TreeNode* >q;
q.push(root);
int depth = 0;
while(!q.empty())
{
++depth;
int count = q.size(); //保存每一层元素个数
while(count--)
{
TreeNode* temp = q.front();
q.pop();
if(temp->left) q.push(temp->left);
if(temp->right) q.push(temp->right);
}
}
return depth;
}
};
剑指 Offer 55 - II. 平衡二叉树
输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
/**
* 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:
bool isBalanced(TreeNode* root) {
return depth(root)!=-1;
}
int depth(TreeNode* root)
{
if (!root) return 0;
int left=depth(root->left);
int right=depth(root->right);
if (left==-1) return -1;
if (right==-1) return -1;
if(abs(left-right)<=1)
{
return max(left,right)+1;
}
else
{
return -1;
}
}
};
剑指 Offer 68 - I. 二叉搜索树的最近公共祖先
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。
/**
* 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* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(!root) return NULL;
if(root==p || root==q) return root;
TreeNode* left=lowestCommonAncestor(root->left,p,q);
TreeNode* right=lowestCommonAncestor(root->right,p,q);
if(left!=NULL && right!=NULL) return root;
if( left != NULL ) return left;
if( right != NULL ) return right;
return NULL;
}
};
剑指 Offer 68 - II. 二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。
递 归 实 现
/**
* 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* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root == NULL)
{
return NULL;
}
if( root == p || root == q)// 找到 p 或 q
{
return root;
}
TreeNode* left=lowestCommonAncestor(root->left,p,q);
TreeNode* right=lowestCommonAncestor(root->right,p,q);
if( left != NULL && right != NULL )
{
return root;
}
if( left != NULL )
{
return left;
}
if( right != NULL )
{
return right;
}
return NULL;
}
};
面试题 04.12. 求和路径
给定一棵二叉树,其中每个节点都含有一个整数数值(该值或正或负)。设计一个算法,打印节点数值总和等于某个给定值的所有路径的数量。注意,路径不一定非得从二叉树的根节点或叶节点开始或结束,但是其方向必须向下(只能从父节点指向子节点方向)。
/**
* 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:
int res=0;
int pathSum(TreeNode* root, int sum) {
if(!root) return 0;
dfs(root,sum);// 从当前结点开始深度遍历,看遍历过程中有没有等于sum的路径
pathSum(root-> left,sum);// 当前结点遍历完没有找到相应路径,开始从左子树开始找
pathSum(root-> right,sum);
return res;
}
void dfs(TreeNode* root, int sum)
{
if(!root) return;
if(root->val == sum) res++;// 先看当前结点值是否等于sum,若等于则找到目标路径,res++
dfs(root->left,sum-root->val);// 查找左子树中有没有满足条件的路径,注意需要减去当前结点的val
dfs(root->right,sum-root->val);
}
};