文章目录
- 剑指offer 04——重建二叉树
- 剑指offer 17——树的子结构
- 剑指offer 18——二叉树的镜像
- 剑指offer 22——从上往下打印二叉树
- 剑指offer 23——二叉搜索树的后序遍历序列
- 剑指offer 24——二叉树中和为某一值的路径
- 剑指offer 26——二叉搜索树与双向链表
- 剑指offer 38——二叉树的深度
- 剑指offer 39——平衡二叉树
- 剑指offer 57——二叉树的下一个节点
- 剑指offer 58——对称的二叉树
- 剑指offer 59——按之字形顺序打印二叉树
- 剑指offer 60——把二叉树打印成多行
- 剑指offer 61——序列化二叉树
- 剑指offer 62——二叉搜索树的第K个节点
剑指offer 04——重建二叉树
题目描述
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
示例:输入
[1,2,3,4,5,6,7],[3,2,4,1,6,5,7]
返回值
{1,2,5,3,4,6,7}
解题思路
1 前序遍历由3部分组成,1.头节点 2.左子树 3.右子树
2.中序遍历由3部分组成,1.左子树 2.头节点 3.右子树
对于每次遍历,需要找到中序遍历头节点的下标
以此找到下次递归的子区间
代码
TreeNode* Construct(vector<int>& pre, int pre_l, int pre_r, vector<int>& vin, int vin_l, int vin_r)
{
//超过边界返回空值
if (vin_l > vin_r) return nullptr;
//左右相等,相等,说明此时为叶子节点,无需再次划分
if (vin_l == vin_r) return new TreeNode(vin[vin_l]);
//根节点下标
int tmp = vin_l;
while (pre[pre_l] != vin[tmp])
++tmp;
//左子树长度
int interger = tmp - vin_l;
TreeNode* node = new TreeNode(pre[pre_l]);
//递归左右子树
node->left = Construct(pre, pre_l + 1, pre_l + interger, vin, vin_l, tmp - 1);
node->right = Construct(pre, pre_l + interger + 1, pre_r, vin, tmp + 1, vin_r);
return node;
}
TreeNode* reConstructBinaryTree(vector<int> pre, vector<int> vin) {
return Construct(pre, 0, pre.size() - 1, vin, 0, vin.size() - 1);
}
剑指offer 17——树的子结构
题目描述
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
解题思路
1.这道题需要用到递归的思路
2.递归的终止条件是a.节点root1为空?
b.节点root2为空?3.递归思路
每一个根节点,都递归作为起始节点,来判断是否包含子树
代码
bool dfs(TreeNode* pRoot1, TreeNode* pRoot2) {
if (!pRoot2) return true;
if (!pRoot1) return false;
//递归查找,每一个节点都相等
return pRoot1->val == pRoot2->val && dfs(pRoot1->left, pRoot2->left) &&
dfs(pRoot1->right, pRoot2->right);
}
//主函数
bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2) {
//非空判断
if (!pRoot1 || !pRoot2) return false;
//递归查找,只要包含任意一个子树
return dfs(pRoot1, pRoot2) || HasSubtree(pRoot1->left, pRoot2) ||
HasSubtree(pRoot1->right, pRoot2);
}
剑指offer 18——二叉树的镜像
题目描述
操作给定的二叉树,将其变换为源二叉树的镜像。
比如:---------源二叉树------------------------镜像二叉树
8 8 / \ / \ 6 10 10 6 / \ / \ / \ / \ 5 7 9 11 11 9 7 5
解题思路
交换每一个节点的子结点
代码
TreeNode* Mirror(TreeNode* pRoot) {
// write code here
if (!pRoot) return nullptr;
//交换子结点
TreeNode* tmp = pRoot->left;
pRoot->left = pRoot->right;
pRoot->right = tmp;
//递归交换子结点
Mirror(pRoot->left);
Mirror(pRoot->right);
return pRoot;
}
剑指offer 22——从上往下打印二叉树
题目描述
从上往下打印出二叉树的每个节点,同层节点从左至右打印。
解题思路
二叉树的层次遍历
必须将每层访问完毕后,才能访问下一层
使用队列,队首每次弹出一个元素,都需要将其左右节点插入队尾
队列为空,说明树节点访问完毕
代码
vector<int> PrintFromTopToBottom(TreeNode* root) {
vector<int> res;
if (!root) return res;
queue<TreeNode*> q;
q.push(root);
while (!q.empty()) {
TreeNode* tmp = q.front();
q.pop();
res.push_back(tmp->val);
//每个节点出栈的时候,放入其左右子结点
if (tmp->left) q.push(tmp->left);
if (tmp->right) q.push(tmp->right);
}
return res;
}
剑指offer 23——二叉搜索树的后序遍历序列
题目描述
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则返回true,否则返回false。假设输入的数组的任意两个数字都互不相同。(ps:我们约定空树不是二叉搜素树)
输入
[4,8,6,12,16,14,10]
返回值
true
解题思路
二叉搜索树的后序遍历,即左、右、根。
先通过根节点找到左右子树的分界点,
如果是从前向后找分界点,则接着从前向后判断,看是否存在比他还小的数,是的话返回false
进一步细分递归,直到达到边界条件: l >= r,返回true
代码
//判断l-r之间的后序遍历是否为二叉搜索树
bool VerifyOP(vector<int>& sequence, int l, int r) {
if (l >= r) return true;
int tag = l;
while (tag <= r) {
if (sequence[tag++] > sequence[r])
break;
}
//左右子树的分界线
tag--;
for (int i = tag + 1; i < r; ++i) {
if (sequence[i] < sequence[r])
return false;
}
return VerifyOP(sequence, l, tag - 1) && VerifyOP(sequence, tag, r - 1);
}
//主函数
bool VerifySquenceOfBST(vector<int> sequence) {
if (sequence.size() == 0) return false;
return VerifyOP(sequence, 0, sequence.size() - 1);
}
剑指offer 24——二叉树中和为某一值的路径
题目描述
输入一颗二叉树的根节点和一个整数,按字典序打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。
输入
{10,5,12,4,7},22
返回值
[[10,5,7],[10,12]]
解题思路
深搜 + 回溯
需要搜索所有的路径
代码
//取别名
using v = vector<int>;
using vv = vector<vector<int>>;
//递归寻找路径
void FindPathDFS(TreeNode* root, int sum, v& path, vv& ret)
{
//先放进来
path.push_back(root->val);
//判断是否值相等,且节点到达了队尾
if (root->val == sum && !root->left && !root->right)
ret.push_back(path);
//递归左右子树
if (root->left) FindPathDFS(root->left, sum - root->val, path, ret);
if (root->right) FindPathDFS(root->right, sum - root->val, path, ret);
//回溯,例如走到了最左边的节点,遍历它的兄弟节点需要将其退出,
//返回上一层的递归,然后将其加入(右孩子)
path.pop_back();
}
vector<vector<int>> FindPath(TreeNode* root, int expectNumber) {
v path;
vv ret;
if (!root) return ret;
//dfs加回溯寻找所有的
FindPathDFS(root, expectNumber, path, ret);
return ret;
}
剑指offer 26——二叉搜索树与双向链表
题目描述
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
解题思路
中序遍历
定义一个两个指针,pre、head。
pre用于存放上次访问的节点,head用于存放返回节点
代码
//指针一定要赋初值,否则会出现位置错误(牛客刷题的同学可以试试)
TreeNode* pre = nullptr, * head = nullptr;
TreeNode* Convert(TreeNode* pRootOfTree) {
if (!pRootOfTree) return nullptr;
//构建树的前后节点指针指向
dfs(pRootOfTree);
//如果需要首尾相连
head->left = nullptr;
pre->right = nullptr;
return head;
}
//中序遍历
void dfs(TreeNode* cur)
{
if (!cur) return;
dfs(cur->left);
//前向节点不为空
if (pre)
pre->right = cur;
else
head = cur;//如果cur为空,头节点赋值
//pre在cur的上一级
cur->left = pre;
pre = cur;
dfs(cur->right);
}
剑指offer 38——二叉树的深度
题目描述
输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
输入
{1,2,3,4,5,#,6,#,#,7}
返回值
4
解题思路
递归左右子树
叶子节点位于0层(相对自身)
当前节点的深度 = 左右子树高度最大值+1
代码
int TreeDepth(TreeNode* pRoot) {
//根节点位于第0层
if (!pRoot) return 0;
//子树的高度
int left = TreeDepth(pRoot->left);
int right = TreeDepth(pRoot->right);
//当前节点高度为左右子树高度的最大值+1
return max(left, right) + 1;
}
剑指offer 39——平衡二叉树
题目描述
输入一棵二叉树,判断该二叉树是否是平衡二叉树。
在这里,我们只需要考虑其平衡性,不需要考虑其是不是排序二叉树
平衡二叉树(Balanced Binary Tree),具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。输入
{1,2,3,4,5,6,7}
返回值
true
解题思路
递归求解
根节点是平衡
左右孩子节点是否平衡
代码
//获取节点深度
int GetDepth(TreeNode* root)
{
if (!root) return 0;
int left = GetDepth(root->left);
int right = GetDepth(root->right);
return max(left, right) + 1;
}
//递归判断每个节点的平衡性
bool IsBalanced_Solution(TreeNode* pRoot) {
if (!pRoot) return true;
int left = GetDepth(pRoot->left);
int right = GetDepth(pRoot->right);
//当前节点平衡性,并向下递归
return abs(left - right) <= 1 && IsBalanced_Solution(pRoot->left)
&& IsBalanced_Solution(pRoot->right);
}
剑指offer 57——二叉树的下一个节点
题目描述
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
解题思路
分类讨论(ps:下面的情况是从前向后依次进行的)
- 存在右节点,那么下一个节点一定位于右子树上,返回下个节点;
- 存在父节点,需要向上找父节点。需要找到一个节点,它是它父节点的左孩子,返回父节点,如果没找到,进入情况3;
- 不存在下一个节点,返回null_ptr;
代码
//获取节点深度
TreeLinkNode* GetNext(TreeLinkNode* pNode) {
if (!pNode) return pNode;
//1.如果存在右孩子,下个节点一定位于右孩子
if (pNode->right) {
pNode = pNode->right;
while (pNode->left)
pNode = pNode->left;
return pNode;
}
//2.向上找下一个节点,这个节点是其父节点的左孩子,
//返回这个节点的父节点。
while (pNode->next) {
TreeLinkNode* root = pNode->next;
if (root->left == pNode)
return root;
pNode = pNode->next;
}
//3.没有右孩子和父节点
return nullptr;
}
剑指offer 58——对称的二叉树
题目描述
请实现一个函数,用来判断一棵二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
解题思路
递归判断
对称的二叉树,每个节点都是对称的
从根节点的左右节点开始判断,向下递归判断左右的节点
代码
//两个节点是否对称,两个节点的子树是否对称
bool isSame(TreeNode* node1, TreeNode* node2) {
//空值判断
if (!node1 && !node2)
return true;
else if (!node1 || !node2)
return false;
//递归判断
return node1->val == node2->val && isSame(node1->left, node2->right)
&& isSame(node1->right, node2->left);
}
bool isSymmetrical(TreeNode* pRoot) {
if (!pRoot) return true;
return isSame(pRoot->left, pRoot->right);
}
剑指offer 59——按之字形顺序打印二叉树
题目描述
请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
输入
{8,6,10,5,7,9,11}
返回值
[[8],[10,6],[5,7,9,11]]
解题思路
使用双栈
头节点进入栈1
栈1不为空,循环栈1,将子结点插入栈2,插入顺序为(左-右)
反之循环栈2,将子结点插入栈1,插入顺序(右-左)
双栈为空,循环结束
代码
vector<vector<int> > Print(TreeNode* pRoot) {
vector<vector<int>> res;
if (!pRoot)
return res;
vector<int> tmp;
//定义双栈
stack<TreeNode*> s1;
stack<TreeNode*> s2;
s1.push(pRoot);
while (!s1.empty() || !s2.empty()) {
//栈1栈2交叉遍历
if (!s1.empty()) {
while (!s1.empty()) {
TreeNode* n = s1.top();
tmp.push_back(n->val);
s1.pop();
//这里需要空值判断,否则出现控制错误
if (n->left) s2.push(n->left);
if (n->right) s2.push(n->right);
}
}
else if (!s2.empty()) {
while (!s2.empty()) {
TreeNode* n = s2.top();
tmp.push_back(n->val);
s2.pop();
if (n->right) s1.push(n->right);
if (n->left) s1.push(n->left);
}
}
res.push_back(tmp);
tmp.clear();
}
return res;
}
剑指offer 60——把二叉树打印成多行
题目描述
从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
输入
{8,6,10,5,7,9,11}
返回值
[[8],[6,10],[5,7,9,11]]
解题思路
使用队列
头节点进入队列
当队列不为空,循环打印
需要考虑到每一层的边界,因此while中需要嵌套一个循环
代码
vector<vector<int> > Print(TreeNode* pRoot) {
vector<vector<int>> res;
vector<int> fl;
if (!pRoot) return res;
queue<TreeNode*> q;
q.push(pRoot);
while (!q.empty()) {
//len确定每层的数量
int len = q.size();
for (int i = 0; i < len; ++i) {
TreeNode* tmp = q.front();
fl.push_back(tmp->val);
q.pop();
//很关键,不要忘记空值判断
if (tmp->left) q.push(tmp->left);
if (tmp->right) q.push(tmp->right);
}
res.push_back(fl);
fl.clear();
}
return res;
}
剑指offer 61——序列化二叉树
题目描述
请实现两个函数,分别用来序列化和反序列化二叉树
二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。
二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。
例如,我们可以把一个只有根节点为1的二叉树序列化为"1,",然后通过自己的函数来解析回这个二叉树。输入
{8,6,10,5,7,9,11}
返回值
{8,6,10,5,7,9,11}
解题思路
***通过前序遍历来序列化二叉树
1.null节点用#表示
2.节点数组后跟一个逗号(’,’)作为数据的分割
***反序列化
1.遇到#返回空节点
2.创造根节点
3.递归创造左右节点(子树)
代码
//前序遍历序列化
char* Serialize(TreeNode* root) {
//作为路径的终点
if (!root) return "#";
string str = to_string(root->val);
str.push_back(',');//作为节点数据的分割点
char* left = Serialize(root->left);
char* right = Serialize(root->right);
char* ret = new char[strlen(left) + strlen(right) + str.size()];
strcpy(ret, str.c_str());
strcat(ret, left);
strcat(ret, right);
return ret;
}
//反序列化
TreeNode* Deserialize(char*& str) {
//返回空节点
if (*str == '#') {
str++;
return nullptr;
}
//创建当前节点
int num = 0;
while (*str != ',') {
num = num * 10 + *str - '0';
str++;
}
//跳过逗号
str++;
TreeNode* node = new TreeNode(num);
//递归创建左右子树
node->left = Deserialize(str);
node->right = Deserialize(str);
return node;
}
剑指offer 62——二叉搜索树的第K个节点
题目描述
给定一棵二叉搜索树,请找出其中的第k小的TreeNode结点。
输入
{5,3,7,2,4,6,8},3
返回值
{4}
解题思路
二叉树的中序遍历
访问节点的时候,若K=1,返回当前节点
否则k = k - 1,继续中序遍历
代码
TreeNode* res = nullptr;
int K = 0;
void dfs(TreeNode* root) {
if (!root || K <= 0) return;
dfs(root->left);
//访问节点,如果K等于1,说明访问的是第一个节点
if (K == 1) res = root;
//访问完了根节点所以K减少1,访问右节点
if (--K > 0) dfs(root->right);
}
TreeNode* KthNode(TreeNode* pRoot, int k) {
if (!pRoot) return nullptr;
K = k;
dfs(pRoot);
return res;
}