目录
二叉树中和为某一值的路径(三)
描述
给定一个二叉树root和一个整数值 sum ,求该树有多少路径的的节点值之和等于 sum 。
1.该题路径定义不需要从根节点开始,也不需要在叶子节点结束,但是一定是从父亲节点往下到孩子节点
2.总节点数目为n
3.保证最后返回的路径个数在整形范围内(即路径个数小于231-1)
数据范围:
0<=n<=1000
−109<=节点值<=109−109<=节点值<=109
假如二叉树root为{1,2,3,4,5,4,3,#,#,-1},sum=6,那么总共如下所示,有3条路径符合要求
示例1
输入:{1,2,3,4,5,4,3,#,#,-1},6
返回值:3
说明:如图所示,有3条路径符合
示例2
输入:{0,1},1
返回值:2
示例3
输入:{1,#,2,#,3},3
返回值:2
解题思路
- 首先已经知道 sum 了,所以我们可以以所有的节点当做根,去找符合的路径。
- 然后我们需要所有的节点当做根,然后寻找符合的路径,所以可以考虑使用深度遍历dfs。
- 先从根节点开始,然后相同的方式去递归左右子节点
AC代码
public class Solution {
int res = 0;
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param root TreeNode类
* @param sum int整型
* @return int整型
*/
public int FindPath (TreeNode root, int sum) {
// write code here
// 空树
if(root == null){
return res;
}
// 从根节点开始 深度遍历
dfs(root, sum);
// 以左右节点为根继续寻找符合的路径
FindPath(root.left, sum);
FindPath(root.right, sum);
return res;
}
public void dfs(TreeNode root, int sum){
if(root == null) return;
// 符合res
if(sum == root.val)
res++;
// 进入子节点继续寻找符合的值
dfs(root.left, sum - root.val);
dfs(root.right, sum - root.val);
}
}
二叉搜索树的第k个节点
描述
给定一棵结点数为n 二叉搜索树,请找出其中的第 k 小的TreeNode结点值。
1.返回第k小的节点值即可
2.不能查找的情况,如二叉树为空,则返回-1,或者k大于n等等,也返回-1
3.保证n个节点的值不一样
数据范围:0≤n≤1000,0≤k≤1000,树上每个结点的值满足0≤val≤1000
进阶:空间复杂度 O(n),时间复杂度 O(n)如输入{5,3,7,2,4,6,8},3时,二叉树{5,3,7,2,4,6,8}如下图所示:
该二叉树所有节点按结点值升序排列后可得[2,3,4,5,6,7,8],所以第3个结点的结点值为4,故返回对应结点值为4的结点即可。
示例1
输入:{5,3,7,2,4,6,8},3
返回值:4
示例2
输入:{},1
返回值:-1
解题思路
- 二叉搜索树要找第k小的节点,可以想到用中序遍历二叉树,得到的序列就是升序的,第 k 小就是序列中第k个节点
- 所以我们可以先中序遍历获取到当前的每个节点的值,然后第 k 小 就是数组中下标 k -1 的元素。
AC代码:
简单粗暴的解法
中序获取节点值的数组 然后通过下标
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param proot TreeNode类
* @param k int整型
* @return int整型
*/
private ArrayList<Integer> res = new ArrayList<>();
public int KthNode (TreeNode proot, int k) {
// write code here
if(proot == null || k == 0) return -1;
Inorder(proot);
if(res.size() < k ) return -1;
// 因为是搜查二叉树 中序遍历之后的结果是升序的 要第 k 小的话就是第 k - 1个
return res.get(k - 1);
}
// 中序遍历
void Inorder(TreeNode root){
if(root!=null){
Inorder(root.left);
res.add(root.val);
Inorder(root.right);
}
}
第二种解法
使用一个计数器, 直接在中序遍历的时候获取到 第k个 节点。
TreeNode res = null;
public int KthNode (TreeNode proot, int k) {
midOrder(proot, k);
if(res == null){
return -1;
}else{
return res.val;
}
}
private int count = 0;
void midOrder(TreeNode root, Integer k){
// root为空 或者 count已经超过 k 了
if(root == null || count > k){
return;
}
// 左 根 右 中序遍历
midOrder(root.left, k);
count++;
// 找到第k个元素
if(count == k){
res = root;
}
midOrder(root.right,k);
}
二叉搜索树与双向链表
描述
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。如下图所示
数据范围:输入二叉树的节点数 0≤n≤1000,二叉树中每个节点的值 0≤val≤1000
要求:空间复杂度O(1)(即在原树上操作),时间复杂度 O(n)注意:
1.要求不能创建任何新的结点,只能调整树中结点指针的指向。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继
2.返回链表中的第一个节点的指针
3.函数返回的TreeNode,有左右指针,其实可以看成一个双向链表的数据结构4.你不用输出双向链表,程序会根据你的返回值自动打印输出
输入描述:
二叉树的根节点
返回值描述:
双向链表的其中一个头节点。
示例1
输入:{10,6,14,4,8,12,16}
返回值:From left to right are:4,6,8,10,12,14,16;From right to left are:16,14,12,10,8,6,4;
说明:输入题面图中二叉树,输出的时候将双向链表的头节点返回即可。
示例2
输入:{5,4,#,3,#,2,#,1}
返回值:From left to right are:1,2,3,4,5;From right to left are:5,4,3,2,1;
说明:
5
/
4
/
3
/
2
/
1
树的形状如上图
解题思路
- 首先这是一个二叉搜索树,所以中序遍历就是从小到大排序的序列就是升序的。
- 所以这里采用中序遍历,创建一个头结点 head 保存第一个节点的值和一个前继节点pre 保存上一个节点。
- 需要考虑第一个节点的值很特殊,这个节点是最左边这个节点,也是最小值。找到这个节点后,初始化 head 和 pre,后续就是 pre 一直跟进,保存的都是前一个节点的值。
- 利用 TreeNode 构成双向链表的具体操作就是,将 pre 的右指针指向当前结点,将当前结点的左指针指向 pre ,然后将 pre 移动到当前结点,一直递归中序遍历,直到最后一个节点。
AC代码
使用中序递归
// 头指针 用来保存最小的那个节点
TreeNode head = null;
// pre 用来中中序遍历的上一个节点
TreeNode pre = null;
public TreeNode Convert(TreeNode pRootOfTree) {
// 老习惯 判空
if(pRootOfTree == null) return null;
// 使用 中序遍历
// 递归最左的节点
Convert(pRootOfTree.left);
// 找到最小值 根 左 右
if(pre == null){
// 初始化头结点和前继节点
head = pRootOfTree;
pre = pRootOfTree;
}else{
// 开始顺序构建链表
// 前继节点的右节点指向 当前节点
pre.right = pRootOfTree;
// 当前结点的左节点指向 前继节点 形成双向链表
pRootOfTree.left = pre;
// pre移动到当前结点 便于连接下一个节点
pre = pRootOfTree;
}
// 递归右节点
Convert(pRootOfTree.right);
return head;
更加符合题意的解法 思想同上 但是没借助pre 和 head节点
public class Solution {
TreeNode pre= null;
TreeNode root=null;
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree ==null) return null;
Convert(pRootOfTree.left);
if(root == null){
root=pRootOfTree;
}
if(pre!=null){
pRootOfTree.left=pre;
pre.right=pRootOfTree;
}
pre=pRootOfTree;
Convert(pRootOfTree.right);
return root;
}
}
重建二叉树
描述
给定节点数为 n 的二叉树的前序遍历和中序遍历结果,请重建出该二叉树并返回它的头结点。
例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建出如下图所示。
提示:
1.vin.length == pre.length
2.pre 和 vin 均无重复元素
3.vin出现的元素均出现在 pre里
4.只需要返回根结点,系统会自动输出整颗树做答案对比
数据范围:0n≤2000,节点的值 −10000≤val≤10000
要求:空间复杂度O(n),时间复杂度 O(n)
示例1
输入:[1,2,4,7,3,5,6,8],[4,7,2,1,5,3,8,6]
返回值:{1,2,3,4,#,5,6,#,7,#,#,8}
说明:返回根节点,系统会输出整颗二叉树对比结果,重建结果如题面图示
示例2
输入:[1],[1]
返回值:{1}
示例3
输入:[1,2,3,4,5,6,7],[3,2,4,1,6,5,7]
返回值:{1,2,5,3,4,6,7}
解题思路
- 题目给出了先序遍历 和 中序遍历的结果数组,我们需要根据这两个序列去构建出二叉树。
- 首先根据先序遍历是可以确定根节点的,先序遍历的第一个元素就是需要构建出的二叉树的根节点。
- 然后去中序遍历中找到根节点的左右子树,以根节点将中序遍历的数组分为三个部分,再去前序中也将数组分为三个部分,三个部分为:根节点,左子树,右子树。
如下图:
- 我们就可以把构建这个树,分解为构建这个树的子树,将一个大问题分解成一个个子问题,解决子问题,最终解决这个大问题,相当于想要构建这个树,我们就去先去构建他的子树,根据它的子树,又去构建子树的子树,最后构建出这个树,这就很明显是个递归的思想,加上分割数组,很容易想到分治法,所以我选用递归和分治的思想去解决这个问题。
本题重在理解这个思想,代码量不是很大,只有想清楚整个构建树的流程,才能更好地理解本题的解题思路。
AC代码
递归
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param preOrder int整型一维数组
* @param vinOrder int整型一维数组
* @return TreeNode类
*/
public TreeNode reConstructBinaryTree (int[] preOrder, int[] vinOrder) {
// write code here
int n = preOrder.length;
int m = vinOrder.length;
// 两个序列必须要有数据
if(m == 0 || n == 0) return null;
// 先序的第一个节点是 根节点
TreeNode root = new TreeNode(preOrder[0]);
for(int i = 0; i < vinOrder.length ; i++){
if(root.val == vinOrder[i]){
// vinOrder[i] 对应的是子树的根节点
// 构建左子树
// 在先序遍历中是根节点的后 i 个元素是左子树 对应中序遍历的前 i 个元素
root.left = reConstructBinaryTree(Arrays.copyOfRange(preOrder, 1 ,i + 1), Arrays.copyOfRange(vinOrder,0, i));
// 构建右子树
// 在先序遍历中是 第 i 个节点的后的所有元素 对应中序遍历的第 i 个元素后的所有元素
root.right = reConstructBinaryTree(Arrays.copyOfRange(preOrder,i + 1, preOrder.length), Arrays.copyOfRange(vinOrder,i + 1, vinOrder.length));
}
}
return root;
}
树的子结构
描述
输入两棵二叉树A,B,判断B是不是A的子结构。(我们约定空树不是任意一个树的子结构)
假如给定A为{8,8,7,9,2,#,#,#,#,4,7},B为{8,9,2},2个树的结构如下,可以看出B是A的子结构
数据范围:
0 <= A的节点个数 <= 10000
0 <= B的节点个数 <= 10000
示例1
输入:{8,8,7,9,2,#,#,#,#,4,7},{8,9,2}
返回值:true
示例2
输入:{1,2,3,4,5},{2,4}
返回值:true
示例3
输入:{1,2,3},{3,1}
返回值:false
解题思路
- 要比较给定子树是否是树的子树,首先可以想到去用 root2 与 root1的每个根节点开始做比较。
- 所以可以用 root1 的每个根节点去与 root2 作比较,查看是否有都相等的树。
- 具体步骤:
- 首先去比较 root1 中每个节点的根节点,如果根节点相等
- 去比较根节点的左右节点是否和 root2 的左右节点是否相等
AC代码
递归
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
// root2 为空树
if(root2 == null)
return false;
// root1 为空 root2不为空
if(root1 == null)
return false;
// 递归比较 从根节点开始
boolean flag1 = recursion(root1, root2);
// 递归 root1 的每个节点
boolean flag2 = HasSubtree(root1.left, root2);
boolean flag3 = HasSubtree(root1.right, root2);
return flag1 || flag2 || flag3;
}
public boolean recursion(TreeNode root1, TreeNode root2){
// 当 root2 不为空 root1 为空
if(root1 == null && root2 != null)
return false;
// 两个都为空 相当于两个树的当前子树都遍历完了
if(root1 == null || root2 == null)
return true;
// 比较节点值
if(root1.val != root2.val)
return false;
// 递归比较子树是否相等
return recursion(root1.left ,root2.left) && recursion(root1.right, root2.right);
}
序列化二叉树
描述
请实现两个函数,分别用来序列化和反序列化二叉树,不对序列化之后的字符串进行约束,但要求能够根据序列化之后的字符串重新构造出一棵与原二叉树相同的树。
二叉树的序列化(Serialize)是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树等遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#)
二叉树的反序列化(Deserialize)是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。例如,可以根据层序遍历的方案序列化,如下图:
层序序列化(即用函数Serialize转化)如上的二叉树转为"{1,2,3,#,#,6,7}",再能够调用反序列化(Deserialize)将"{1,2,3,#,#,6,7}"构造成如上的二叉树。
当然你也可以根据满二叉树结点位置的标号规律来序列化,还可以根据先序遍历和中序遍历的结果来序列化。不对序列化之后的字符串进行约束,所以欢迎各种奇思妙想。
数据范围:节点数 n≤100,树上每个节点的值满足 0≤val≤150
要求:序列化和反序列化都是空间复杂度O(n),时间复杂度O(n)
示例1
输入:{1,2,3,#,#,6,7}
返回值:{1,2,3,#,#,6,7}
说明:如题面图
示例2
输入:{8,6,10,5,7,9,11}
返回值:{8,6,10,5,7,9,11}
解题思路
- 根据题目可以知道这题解法有很多,只要保证序列化成的字符串能反序列化成树就行,可以用先序,中序,后序,层序等等。
- 本题我是使用的层序,利用了数据结构-队列来辅助完成层序方式的序列化和反序列化。
- 首先我定义了空节点和它的值便于对空节点的定义和序列化和反序列化的进行。
具体步骤:
- 首先从树序列化成字符串,可以使用 StringBuilder 来拼接每个节点的值,最后转成字符串。
- 判断 root 节点是否为空 返回字符串
- 使用一个循环将整个树每个节点的值拼接成一个字符串,加了分隔符 - ,便于后续获取字符串数组。
- 创建一个队列来实现层序遍历这个树
- 创建一个 StringBuilder 用于拼接字符串
- 从字符串序列化为树,怎么序列化的就怎么反序列化
- 返回根节点的值
- 对取出的值做判断,不等于INF,入队加上构建成为左右节点。
- 使用一个 for 循环去拿到字符串中的值,每次拿两个,一个是左节点的值,一个是右节点的值。
- 数组的第一个值就作为根节点的值创建根节点
- 创建一个队列实现以层序方式构建这个树
- 根据序列化时候的分隔符得到字符串数组,便于后续的构建树
- 判断是否为空字符串
AC代码
队列
// 当做空节点的值
int INF = 0x3f3f3f3f;
// 空节点
TreeNode emptyNode = new TreeNode(INF);
String Serialize(TreeNode root) {
if(root == null) return "";
// 存放序列化后的节点值
StringBuilder str = new StringBuilder();
// 使用队列 层序遍历
Deque<TreeNode> d = new ArrayDeque<>();
// 根节点入队
d.addLast(root);
while(!d.isEmpty()) {
// 取出相应节点的值开始拼接字符串
TreeNode tmp = d.pollFirst();
// 拼接字符串 使用 - 作为分隔符
str.append(tmp.val + "-");
if(tmp != emptyNode) {
// 将当前结点的左右节点入队 便于下一次的拼接
d.addLast(tmp.left != null ? tmp.left : emptyNode);
d.addLast(tmp.right != null ? tmp.right : emptyNode);
}
}
return str.toString();
}
TreeNode Deserialize(String str) {
if("".equals(str)) return null;
// 根据分割分进行分割
String[] s = str.split("-");
int length = s.length;
// 序列化的时候是用的层序遍历那反序列化就用层序遍历
// 将第一个值作为根节点的值
TreeNode root = new TreeNode(Integer.parseInt(s[0]));
Deque<TreeNode> d = new ArrayDeque<>();
d.addLast(root);
// 因为是层序遍历 根 左 右 每次从根节点往后遍历两个值 取出左右两个值
for(int i = 1;i < length - 1; i += 2){
// 当前根节点出队
TreeNode cur = d.pollFirst();
int left_value = Integer.parseInt(s[i]);
int right_value = Integer.parseInt(s[i + 1]);
// 判断左右节点的值是否为 INF
if(left_value != INF){
// 不为INF 将其作为左节点
cur.left = new TreeNode(left_value);
// 添加到队列中 便于后续层序遍历
d.addLast(cur.left);
}
if(right_value != INF){
// 不为INF 将其作为右节点
cur.right = new TreeNode(right_value);
// 添加到队列中 便于后续层序遍历
d.addLast(cur.right);
}
}
return root;
}