1.二叉树"层序遍历"的知识点
1.层序遍历——>>树的深度
listsRes.size() 就是本二叉树的深度,因为它代表层数
2.层序遍历——>>树的最大宽度
暂存队列list.size() 的最大值就是二叉树最大的宽度
- listsRes.add(list);前加入
max = list.size>max ? list.size() : max ; //在每层结束后都判断max是否需要更新
3.本程序是层序遍历,且"自顶向下"按层输出
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> listsRes = new LinkedList<>();
1.处理特殊情况
if (root==null) return listsRes;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root); 队列先入先出
while (queue.size()!=0){
2.辅助队列,暂时存储本层的数据,待本层遍历结束后,把数据存进listRes,开启下次循环前重新更新
List<Integer> list = new LinkedList<>(); //辅助队列
2.1巧妙的分层方法,循环遍历queue.size()次数
for (int i = queue.size(); i >0 ; i--) {
TreeNode node = queue.remove();//队列先入先出
list.add(node.val);
2.2判断并添加新的下级子节点
if (node.left!=null) queue.add(node.left);
if (node.right!=null) queue.add(node.right);
}
3.把每层的数值添加进去
listsRes.add(list);
}
return listsRes;
}
}
4.层序遍历——自底向上,按层输出
- 跟上一个程序的区别在于:
采用双向队列deque,每层遍历结束之后,把该层元素添加至 队首即可。
class Solution {
public List<List<Integer>> levelOrderBottom(TreeNode root) {
0.双向队列,可以把元素添加到 队首
Deque<List<Integer>> listsRes = new LinkedList<>();
1.处理特殊情况
if (root==null) return new LinkedList<>() ;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root); 队列先入先出
while (queue.size()!=0){
2.辅助队列,暂时存储本层的数据,待本层遍历结束后,把数据存进listRes,开启下次循环前重新更新
List<Integer> list = new LinkedList<>(); //辅助队列
2.1巧妙的分层方法,循环遍历queue.size()次数
for (int i = queue.size(); i >0 ; i--) {
TreeNode node = queue.remove();//队列先入先出
list.add(node.val);
2.2判断并添加新的下级子节点
if (node.left!=null) queue.add(node.left);
if (node.right!=null) queue.add(node.right);
}
3.把每层的数值添加进去,而且要添加到队首!!
listsRes.addFirst(list);
}
return (List)listsRes;
}
}
5.层序遍历Z字形输出——自顶向下按层输出,偶数层反转
- 跟3的区别在于:
1.加入反转标志:boolean flagReverse
2.采用反转方法:Collections.reverse(list);
对list数组进行反转!
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> listsRes = new LinkedList<>();
1.处理特殊情况
if (root==null) return listsRes;
1.5 加入反转标志
boolean flagReverse = false;
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root); 队列先入先出
while (queue.size()!=0){
2.辅助队列,暂时存储本层的数据,待本层遍历结束后,把数据存进listRes,开启下次循环前重新更新
List<Integer> list = new LinkedList<>(); //辅助队列
2.1巧妙的分层方法,循环遍历queue.size()次数
for (int i = queue.size(); i >0 ; i--) {
TreeNode node = queue.remove();//队列先入先出
list.add(node.val);
2.2判断并添加新的下级子节点
if (node.left!=null) queue.add(node.left);
if (node.right!=null) queue.add(node.right);
}
3.把每层的数值添加进去
3.1反转标志,每隔一层反转一次
if (flagReverse) Collections.reverse(list);
flagReverse = !flagReverse;
listsRes.add(list);
}
return listsRes;
}
}
2. 二叉树前、中、后序遍历(如何写出他们仨的迭代方法)
0.二叉树遍历框架
void traverse(TreeNode root) {
1.前序遍历
traverse(root.left)
2.中序遍历
traverse(root.right)
3.后序遍历
}
1.中序遍历:(左—>中—>右 或者 右—>中—>左)
1.1,对于二叉搜索树
- 1.左中右 遍历的list是升序排列;
- 2.右中左 遍历的list是降序排列。
1.1.1.下边的例题可以采用中序遍历——右-中-左——顺序
1.基于中序遍历的变体专门针对二叉搜索树降序、升序排列的
class Solution {
public int kthLargest(TreeNode root, int k) {
List<Integer> list = new ArrayList<>();
inorder(root,list,k);
return list.get(k-1);
}
二叉树的中序遍历——(右 中 左)
在二叉搜索树中这样得到的list是按降序排列的
public void inorder(TreeNode node,List list, int k){
if (node==null || list.size()>k) return;
inorder(node.right,list, k);
list.add(node.val);
inorder(node.left,list, k);
}
}
3.二叉树的深度
1.可以基于层序遍历(不再描述)
2.可以基于递归算法
- 注意
- 1.int maxDepth = 0; 必须定义到类体,
因为如果作为参数传入回溯方法,更新后的maxDepth并没有被再传出去,所以一直会是0!!
- 1.int maxDepth = 0; 必须定义到类体,
class Solution {
int maxDepth = 0;
public int maxDepth(TreeNode root) {
1.处理特殊情况
if (root==null) return 0;
int tempDepth=0;
backtrack(root,tempDepth);
return maxDepth;
}
public void backtrack(TreeNode node, int tempDepth){
1.结束条件
if (node==null) return;
2.做出选择 ++
tempDepth++;
backtrack(node.left,tempDepth);
backtrack(node.right,tempDepth);
2.5更新数据
if (tempDepth>maxDepth) maxDepth=tempDepth;
3.撤销选择 --
tempDepth--;
}
}
4.对称二叉树
1.什么是对称二叉树?(他的所有子树也都应该是对称的!)
- 1.除根节点外,其他层的结点数为偶数;
- 2.每层节点从外向里,数值相同,做右节点不同(即不能同是上一层的左节点,或同是右节点)
- 3.满足条件1、2的,在数值相同时,还要满足各自的父节点在上一层的下标之和 = 上一层宽度-1
2.解题思路
- 1.可以选择层序遍历,然后按照对称二叉树的条件1、2、3,来进行;
- 2.采用递归思路,扩散递归。
- 注意:观察下列递归函数,可知传入的是2个参数,return的是4个参数 所以这个递归是指数级扩张的。
class Solution {
public boolean isSymmetric(TreeNode root) {
if (root==null) return true;
return recursion(root.left,root.right);
}
public boolean recursion(TreeNode left,TreeNode right){
终止条件:1.两个节点是 空
if (left==null && right==null)
return true;
终止条件:2. 其中一个节点为空,另一个不为空
或 两个节点的数值不同
if (left==null || right==null ||left.val!= right.val)
return false;
*******************************************************
递归:3。出于对称位置的节点 进入同一递归函数
而且 应该用 && 符号连接
return recursion(left.left,right.right) && recursion(left.right,right.left);
}
}
5.二叉树的镜像
1.什么是二叉树的镜像?
- 原来二叉树每个节点的左右子节点进行交换。
力扣-二叉树镜像解法
2.考虑递归解法
- 注意 :此递归解法,传入1个参数,又有2个参数参与递归,所以是指数级扩张递归!
class Solution {
public TreeNode mirrorTree(TreeNode root) {
0.终止条件
if (root==null) return null;
1.左右节点反转
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
2.进入递归,而且是扩散式递归
mirrorTree(root.left);
mirrorTree(root.right);
3.结束
return root;
}
}
6.剑指 Offer 55 - II. 平衡二叉树
1.什么是平衡二叉树?
- 1.任意节点的树的深度差值 都要 <= 1.
- 2.其实还要保证该树是二叉排序树(即:中序遍历(左中右)的list数组是严格递增即可!)(但本题不要求)
力扣-答案
2.解题思路
- 1.从根节点开始递归所有子节点,并检查所有子节点的左右子树的高度差值!
如下图所示,根节点符合要求,但节点4不符合要求,所以不是平衡二叉树
- 注意:本递归程序也是传入1个参数,return回2个参数,所以是扩散式递归
class Solution {
public boolean isBalanced(TreeNode root) {
0.终止条件
if (root==null) return true;
1.检查当前节点的左右子树的 深度差值
if (Math.abs(treeDepth(root.left)-treeDepth(root.right))>1)
return false;
2.检查当前节点的左右节点,是否也是平衡二叉树
因为只要任意子树不是平衡二叉树,那整棵树都不是平衡二叉树
return isBalanced(root.left) && isBalanced(root.right);
}
求树的深度
public int treeDepth(TreeNode node){
if (node==null) return 0;
return Math.max(treeDepth(node.left),treeDepth(node.right))+1;
}
}
7.面试题34. 二叉树中和为某一值的路径(回溯算法思想)
1.回溯算法的套路:
- 1.路径:就是已经做出的选择;
- 2.选择列表:就是当前可以做的选择;
- 3.结束条件:也就是达到决策层,无法再做选择的条件。
2.回溯算法代码框架:
result = []
void backTrack(路径,选择列表){
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择;
backTrack(路径,选择列表);
撤销选择
}
3.此题思路:
- 1.建立一个返回lists列表,建立一个临时路径列表tempList,建立变量tempSum存储此路径的和;
- 2.当 1.到达叶子节点、2.且sum=target时,那当前路径tempList存入lists列表。
注意:在把tempList存入lists时,要新new一个list,不然即使存入lists了,但其中的tempList的内容还是会改变。
class Solution {
List<List<Integer>> lists = new ArrayList<>();
List<Integer> tempList = new ArrayList<>();
int tempSum = 0;
public List<List<Integer>> pathSum(TreeNode root, int target) {
if (root==null) return new ArrayList<>();
trackBack(root,target);
return lists;
}
public void trackBack(TreeNode root,int target){
0.终止条件
if (root==null) return;
1.做出选择
tempSum += root.val;
tempList.add(root.val);
2.调用递归
trackBack(root.left, target);
trackBack(root.right,target);
2.5做判断,这里不仅确保 tempSum=target 还要确保是 叶子节点
if (tempSum==target && root.right==null && root.left==null){
//重新new一个list,是因为如果直接存tempList,存的只是他的地址,而他的内容并不固定
lists.add(new ArrayList<>(tempList));
}
3.撤销选择
tempSum -=root.val;
tempList.remove(tempList.size()-1);
}
}
8.剑指 Offer 68 - II. 二叉树的最近公共祖先
0.递归解题思路
(注意这个题目的设定是p、q一定是存在于树中的)
- 1.在递归解法中,被寻找到的p、q会被一层层的 return 上去
- 2.在 p 、q 两个节点被一层层向上return的过程中,当两者首次会合时
即:if (left != null && right!=null) 满足此条件时,那么向上只return这个让两者首次回合的节点root。 - 3.如果p 、 q最后在根节点回合的,那就 根节点就是公共节点。
- 4.假设p在q的子树中,那么就没有回合发生了,直接就向上return节点q就可以,因为他就是最近公共节点。
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
0.终止条件
if (root==null) return null;
0.1 发现p(或者q) 就直接向上return了,
如果q 在p下的子树中,也不必担心,因为p就是最近公共节点了
if (root==p || root==q) return root;
0.5 递归 左右子树 是否有公共节祖先
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right= lowestCommonAncestor(root.right, p, q);
1.如果左右同时为空,说明左右子树均不包含节点 p q
if (left == null && right==null) return null;
2.说明p q 都不在左子树中
if (left == null) return right;
3.说明p q 都不在右子树中
if (right== null) return left;
4.说明p q分别在左右子树 中,所以当前节点就是 最近公共节点
// if (left != null && right!=null) //这句判断 存不存在都一样
return root;
}
}
1.解题思路:
- 1.先找到节点p、q两个节点,最浅的深度lowDepth(假设这是p的深度);
- 2.从此深度上的p开始,以p为根节点进行遍历,如果找到q,则p符合要求;
- 3.如果没有从上一深度从头遍历,看是否能同时含有p或者q。
class Solution1 {
Deque<Deque<TreeNode>> level = new LinkedList<>();
int flag = 0;
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root==null) return null;
TreeNode res = new TreeNode();
levelTravel(root,p,q);
for (Deque<TreeNode> list:level) {
for (int i = list.size(); i > 0 ; i--) {
flag=0;更新标志位
res = list.removeLast();
backTrack(res, p,q);
if (flag==2)
return res;
}
}
return null;
}
层序遍历,找到p q 两者最浅层 及其之前的元素
public void levelTravel(TreeNode root,TreeNode p,TreeNode q){
if (root==null) return;
Deque<TreeNode> list = new LinkedList<>();
list.add(root);
Deque<TreeNode> temp = new LinkedList<>();
while (!list.isEmpty()){
for (int i = list.size(); i > 0 ; i--) {
TreeNode node = list.remove();
temp.add(node);
if (node == p || node==q){
level.addFirst(temp); 这里应该 下边的层 添加在 前边,这样遍历时,就由深层 到其按层遍历
return;
}
if (node.left!=null) {
list.add(node.left);
}
if (node.right!=null){
list.add(node.right);
}
}
level.addFirst(temp); 这里应该 下边的层 添加在 前边,这样遍历时,就由深层 到其按层遍历
}
}
遍历节点是否同时含有p q
public void backTrack(TreeNode node, TreeNode p,TreeNode q){
终止条件
if (node==null) return;
检查是否有 p q
if (node==p || node== q) flag++;
backTrack(node.left, p, q);
backTrack(node.right, p, q);
}
}
2.剑指 Offer 68 - I. 二叉搜索树的最近公共祖先
注意:这是二叉搜索树,更简单一点。
1.解题思路:
- 1.二叉搜索树是严格按大小排列的,左< 根 < 右
- 2.如果节点x 是 节点p、q (假设 p<q)的最近公共节点,那么一定有 p < x < q
- 3.如果节点x 是公共节点 但不是最近的,那就会有 x < p < q 或者 x > q > p
代码就像在比大小
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root==null) return null;
TreeNode ancestorLowest = root;
while (true) {
if (ancestorLowest.val < p.val && ancestorLowest.val < q.val)
ancestorLowest = ancestorLowest.right;
else if (ancestorLowest.val > p.val && ancestorLowest.val > q.val)
ancestorLowest = ancestorLowest.left;
else
break;
}
return ancestorLowest;
}
}
9.剑指 Offer 26. 树的子结构(这个题不会!)(双方法递归)
1.思路:
- 1.先构造1个辅助递归方法recur(),来比较输入的A、B根节点 ,它们的各个节点值是否相同,(当某节点B为null,A不是null时,也算是相同);
- 2.在主方法内也进行递归,主要是判断:
-
- A 、B是否为null,如果是则为false;(因为规定,空树不是任何树的子结构)
-
- 把A、B 送入辅助递归方法recur(),具体判断节点是否相同;
-
- 如果A、B 为根节点的结构无法匹配,那么A.left 、A.right 这两个分别为根节点再和 根节点B去匹配。
- 如果A、B 为根节点的结构无法匹配,那么A.left 、A.right 这两个分别为根节点再和 根节点B去匹配。
注意:这是两个不同方法 分别进行扩散递归
class Solution {
public boolean isSubStructure(TreeNode A, TreeNode B) {
1.判断 A、B是否为 null ;
2.选定A树上的某个节点node,与B进行具体的节点值比较递归;
3.当前节点node失败后,再把node的左右子节点分别送入主方法
return (A!=null && B!=null) && (recuur(A,B) || isSubStructure(A.left,B) || isSubStructure(A.right,B));
}
public boolean recuur(TreeNode A, TreeNode B){
1.说明B的节点已经匹配完
if (B==null) return true;
2.若A先为空 或者 节点值不一样 返回false
if (A==null || A.val!=B.val) return false;
3.比较A B 的左右子树是否相等
return recuur(A.left,B.left) && recuur(A.right,B.right);
}
}
10.剑指 Offer 07. 重建二叉树
1.思路:
- 1.二叉树的前序遍历(根左右)数组的第1个数,就是根节点root;
- 2.根据前序遍历得出的root,再到中序遍历(左根右)数组中,以root为界限,分出左右子树的数组,即左子数组、右子数组。
- 3.然后依次递归即可。
注意:核心其实时如何把前序遍历 中序遍历数组给分成左右子树的,
如果要划分,其中最重要的又是找到 根节点 在中序遍历数组的下标rootIndex,而且你会发现rootIndex还可以用于划分前序数组为左右子树的下标。
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
0.处理特殊情况
if (preorder.length==0 || inorder.length==0) return null;
0.5.创建根节点root
TreeNode root = new TreeNode(preorder[0]);
找出root节点在中序遍历数组的下标
int rootIndex=0;
在中序遍历表中寻找 根节点root 所在位置下标,并记录
for (int i = 0;i<inorder.length;i++){
if (inorder[i]==preorder[0]){
rootIndex=i;
break;
}
}
2.左右子树的各自 中序遍历 子数组
int[] left_inorder = Arrays.copyOfRange(inorder,0,rootIndex);
int[] right_inorder = Arrays.copyOfRange(inorder,rootIndex+1,inorder.length);
3.找到左右子树的各自 前序遍历 子数组
int[] left_pre = Arrays.copyOfRange(preorder,1,1+rootIndex-0);
int[] right_pre = Arrays.copyOfRange(preorder,rootIndex+1,preorder.length);
4.根节点root的左右子树,开启递归
root.left = buildTree(left_pre,left_inorder);
root.right= buildTree(right_pre,right_inorder);
return root;
}
}