第六章 二叉树 part02
今日内容:
● 层序遍历 10
● 226.翻转二叉树
● 101.对称二叉树 2 详细布置
层序遍历
102.二叉树的层序遍历
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result=new ArrayList<>();
//如果根节点为空,则直接return
if(root==null)
return result;
//创建队列
Queue<TreeNode> queue=new LinkedList<>();
//curLevelCount用于记录当前遍历的层数的节点个数
//nextLevelCount用于记录下一层的节点个数
int curLevelCount=0;
int nextLevelCount=0;
queue.offer(root);
//因为把根节点加入到队列中,所以当前层数是1
curLevelCount++;
while(!queue.isEmpty()){
TreeNode curNode=new TreeNode();
List<Integer> levelValList=new ArrayList<>();
//只要当前层数的节点没有遍历完
while(curLevelCount>0){
//出队
curNode=queue.peek();
queue.poll();
//出队一个,则当前层数的节点个数就要减少一个
curLevelCount--;
levelValList.add(curNode.val);
//将其左右子节点入队————即下一层每入队一个,nextLevelCount就++
if(curNode.left!=null){
queue.offer(curNode.left);
nextLevelCount++;
}
if(curNode.right!=null){
queue.offer(curNode.right);
nextLevelCount++;
}
}
//当跳出上面的那个循环之后,代表当前层遍历结束
//就要将元素加入到result中
result.add(levelValList);
//同时层就要往下切换,此时curLevelCount就等于原定的nextLevelCount
//而nextLevelCount就为0 又要重新计算
curLevelCount=nextLevelCount;
nextLevelCount=0;
}
return result;
}
}
注意: 这上面有一个要注意的点,就是下面的这一行代码。虽然看起来很奇怪,但是从Java 7开始可以用菱形操作符(<>)来自动推断泛型类型。这样就简洁了很多。
List<List<Integer>> result=new ArrayList<>();
这里使用Carl哥的迭代方法也是更为简介。一开始也是只想用一个临时变量来完成“层级”变化时数量的记录。但是一直没有想到。
Carl哥这个二叉树层序遍历与我上面代码略有不同的地方在于——其获取到的len=queue.size()就是当前访问层的节点个数,即便我在第二层while循环中,新加入了下一层节点,但是只要我当前层节点个数未访问完,便不会去更新len的值。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result=new ArrayList<>();
if(root==null)
return result;
Queue<TreeNode> queue=new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
int len=queue.size();
TreeNode curNode=new TreeNode();
List<Integer> itemList = new ArrayList<Integer>();
while(len>0){
curNode=queue.poll();
len--;
itemList.add(curNode.val);
if(curNode.left!=null)
queue.offer(curNode.left);
if(curNode.right!=null)
queue.offer(curNode.right);
}
result.add(itemList);
}
return result;
}
}
2、二叉树层序遍历——使用递归方法
对于递归不太懂,这里先记录下来:
// 102.二叉树的层序遍历
class Solution {
public List<List<Integer>> resList = new ArrayList<List<Integer>>();
public List<List<Integer>> levelOrder(TreeNode root) {
checkFun01(root,0);
return resList;
}
//DFS--递归方式
public void checkFun01(TreeNode node, Integer deep) {
if (node == null) return;
deep++;
if (resList.size() < deep) {
//当层级增加时,list的Item也增加,利用list的索引值进行层级界定
List<Integer> item = new ArrayList<Integer>();
resList.add(item);
}
resList.get(deep - 1).add(node.val);
checkFun01(node.left, deep);
checkFun01(node.right, deep);
}
}
226.翻转二叉树 (优先掌握递归)
这道题对于我个人来说是一个掌握递归的好方法:
先看题解:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode invertTree(TreeNode root) {
if(root==null)
return root;
reverseTree(root);
return root;
}
public void reverseTree(TreeNode node){
if(node==null)
return;
TreeNode temp=node.right;
node.right=node.left;
node.left=temp;
reverseTree(node.left);
reverseTree(node.right);
}
}
-
牢记递归三要素:(真的很重要)
-
-
确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
-
确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
-
确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
-
-
-
对应到这道题:
直接看代码注释:
//递归三要素之NO.1 ————确定递归函数的参数和返回值
//由于本题只是翻转二叉树,而不存在对二叉树内部的数值进行操作,也不需要存储什么东西,故而递归函数的参数就是二叉树的节点Node,返回值为void
public void reverseTree(TreeNode node){
//递归三要素之NO.2————确定终止条件
//什么时候,不需要执行翻转操作,而可以return呢,就是访问到了叶子节点的下一个,即空节点
if(node==null)
return;
//递归三要素之NO.3————确定单层递归的逻辑
//根据题意,本题在每一层递归的过程中,需要处理的就是交换左右子节点。注意:是交换左右子节点,而不是交换左右子节点的val
TreeNode temp=node.right;
node.right=node.left;
node.left=temp;
reverseTree(node.left);
reverseTree(node.right);
}
个人递归推导过程:
同时其实可以大胆尝试,经过这个推导,其实可以发现——并不一定非要遵循二叉树的前序遍历顺序or后序遍历顺序。
这样子写也是可以通过的:
public void reverseTree(TreeNode node){
if(node==null)
return;
TreeNode temp=node.right;
node.right=node.left;
node.left=temp;
reverseTree(node.right);
reverseTree(node.left);
}
101. 对称二叉树 (优先掌握递归)
题目位置:101.对称二叉树
个人错误思路记录:
这里我错误地考虑直接将二叉树用中序遍历,然后使用双指针的方法判断是否对称。
这个方法错误的地方在于:没有细节地考虑到二叉树的某些特殊结构。比如:
上面的这个二叉树用中序遍历之后,其元素也是对称的。 但是肉眼即可发现,它整个二叉树的结构并不是镜像的。
错误的代码如下:
class Solution {
public boolean isSymmetric(TreeNode root) {
//经过模拟,可以发现,如果将这棵二叉树进行中序遍历,就可以发现规律
//如果整棵树只有根节点,那它也是对称的
if(root.left==null && root.right==null)
return true;
List<Integer> valList=new ArrayList<>();
inOrder(root,valList);
//通过上面调用inOrder函数,已经将二叉树的中序遍历结果放入到valList中
//下面是使用双指针,来判断元素是否相等
int j=valList.size()-1;
int i=0;
while(i!=j){
if(valList.get(i)!=valList.get(j))
return false;
i++;
j--;
}
return true;
}
public void inOrder(TreeNode node,List<Integer> list){
//中序遍历————左中右
if(node==null)
return;
inOrder(node.left,list);
list.add(node.val);
inOrder(node.right,list);
}
}
正确的思路应该为:
在抛开根节点的情况下,将根节点的左右子树认为是两棵树,然后分别对其里侧和外侧遍历进行比较。
以下为Carl哥的图,借用
此时上图中要比较的分别就是 左树的左子节点 与 右树的右子节点 值是否相等;左树的右子节点 和 右树的左子节点 值是否相等;