翻转二叉树
例题226:给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。
//①层序遍历
class Solution {
public TreeNode invertTree(TreeNode root) {
if(root==null) return null;
Queue<TreeNode> que=new LinkedList<TreeNode>();
que.add(root);
while(!que.isEmpty()){
int len=que.size();
while(len>0)
{
TreeNode curNode=que.poll();
if(curNode.left!=null || curNode.right!=null){//直接交换左右孩子节点
TreeNode t=curNode.left;
curNode.left=curNode.right;
curNode.right=t;
}
if(curNode.left!=null) que.add(curNode.left);
if(curNode.right!=null) que.add(curNode.right);
len--;
}
}
return root;
}
}
层序遍历二叉树直接交换每一个节点的左右孩子
//②递归遍历
if(root==null) return null;
TreeNode t=root.left;
root.left=root.right;
root.right=t;
invertTree(root.left);
invertTree(root.right);
return root;
//③迭代遍历
**if(root==null) return null;
Stack<TreeNode> stack=new Stack<TreeNode>();
stack.push(root);
while(!stack.empty()){
TreeNode curNode=stack.peek();
if(curNode!=null){
stack.pop();
TreeNode t=curNode.left;
curNode.left=curNode.right;
curNode.right=t;
if(curNode.right!=null) stack.push(curNode.right);
if(curNode.left!=null) stack.push(curNode.left);
stack.push(curNode);
stack.push(null);
}
else{
stack.pop();
curNode=stack.pop();
}
}
return root;
N叉树的前序遍历
例题589:给定一个 n 叉树的根节点 root ,返回 其节点值的 前序遍历 。
n 叉树 在输入中按层序遍历进行序列化表示,每组子节点由空值 null 分隔(请参见示例)。
class Solution {
List<Integer> res=new ArrayList<>();
public List<Integer> preorder(Node root) {
//递归前序遍历
if(root==null) return res;
preTraversal(root);
return res;
}
public void preTraversal(Node root)
{
res.add(root.val);
if(root.children!=null){
for(int i=0;i<root.children.size();i++){
preTraversal(root.children.get(i));
}
}
}
//迭代法
/*
Stack<Node> stack=new Stack<Node>();
if(root!=null) stack.push(root);
while(!stack.empty()){
Node tNode=stack.peek();
if(tNode!=null){
stack.pop();
for(int i=tNode.children.size()-1;i>=0;i--){
stack.push(tNode.children.get(i));//以遍历相反的顺序入栈
}
stack.push(tNode);
stack.push(null);
}
else{
stack.pop();
tNode=stack.pop();
res.add(tNode.val);
}
}
return res;
*/
}
N叉树的后序遍历
例题590:给定一个 n 叉树的根节点 root ,返回 其节点值的 后序遍历 。
n 叉树 在输入中按层序遍历进行序列化表示,每组子节点由空值 null 分隔(请参见示例)。
class Solution {
List<Integer> res=new ArrayList<>();
public List<Integer> postorder(Node root) {
//递归遍历
/*
if(root==null) return res;
postTraversal(root);
return res;
}
public void postTraversal(Node root){
if(root.children!=null){
for(int i=0;i<root.children.size();i++){
postTraversal(root.children.get(i));
}
res.add(root.val);
}
*/
//迭代遍历
Stack<Node> stack = new Stack<Node>();
if (root != null) stack.push(root);
while (!stack.empty()) {
Node curNode = stack.peek();
if (curNode != null) {
stack.pop();
stack.push(curNode);
stack.push(null);
if (curNode.children != null) {
for (int i = curNode.children.size()-1; i >=0; i--) {
stack.push(curNode.children.get(i));
}
}
} else {
stack.pop();
curNode = stack.pop();
res.add(curNode.val);
}
}
return res;
}
}
对称二叉树
例题101:给你一个二叉树的根节点 root , 检查它是否轴对称。
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root==null) return true;
boolean re;
List<Integer> res=new ArrayList<>();
re=Traversal(root.left,root.right);
return re;
}
boolean Traversal(TreeNode l,TreeNode r){
if(l==null && r!=null) return false;
else if(l!=null && r==null) return false;
else if(l==null && r==null) return true;
else if(l.val!=r.val) return false;
boolean re=Traversal(l.left,r.right);//比较左右子树的外侧
boolean res=Traversal(l.right,r.left);//比较内侧
return re&&res;
}
}
1.在递归中比较的不是每个节点的左右孩子,而是根节点的左右子树的内外侧是否相等;
2.迭代法使用队列,将左右子树内外侧的值加入队列,一对一对取出来比较值是否相同。
相同的树
例题100:给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
//递归法
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
//遍历比较节点相同
if(p==null && q!=null) return false;
else if(p!=null && q==null) return false;
else if(p==null && q==null) return true;
else if(p.val!=q.val) return false;
boolean re=isSameTree(p.left,q.left);
boolean res=isSameTree(p.right,q.right);
return re&&res;
}
}
另一颗树的子树
例题572:给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false 。
二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。
class Solution {
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
return dfs(root, subRoot);
}
public boolean dfs(TreeNode s, TreeNode t) {
if (s == null) {
return false;
}
return check(s, t) || dfs(s.left, t) || dfs(s.right, t);
}
public boolean check(TreeNode s, TreeNode t) {
if (s == null && t == null) {
return true;
}
if (s == null || t == null || s.val != t.val) {
return false;
}
return check(s.left, t.left) && check(s.right, t.right);
}
}
深度优先遍历的暴力解:比较root中每个节点及节点左右孩子下的子树是否与subRoot相等。
二叉树的最大深度
例题104:给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
迭代法上章节层序遍历已做过
//递归法
int res;
res=getDepth(root);
return res;
}
int getDepth(TreeNode root){
if(root==null) return 0;
int dep=getDepth(root.left);//左
int dep2=getDepth(root.right);//右
int depth=Math.max(dep,dep2)+1;//中:加上当前节点这层
return depth;
//迭代法
二叉树的最小深度
例题111:迭代法上章节层序遍历已做过
递归:最小深度与最大深度的递归有所不同
如果按照最大深度的递归写法:
nt dep=getDepth(root.left);//左
int dep2=getDepth(root.right);//右
int depth=Math.min(dep,dep2)+1;//中:加上当前节点这层
return depth;
会出现下图的情况:找到二叉树中最短的一条路,而不是到叶子节点的路径
所有,最小深度的递归法有所不同的是:
①当左节点为空,而右节点不为空时:取左子树深度
②当右节点为空,而左节点不为空时:取右子树深度
③当左右节点都不为空时:取两边子树最小深度
public int minDepth(TreeNode root){
return getDepth(root);
}
public int getDepth(TreeNode root){
int mindep=0;
if(root==null) return mindep;
int ldep=getDepth(root.left);
int rdep=getDepth(root.right);
if(root.left==null && root.right!=null) return rdep+1;
else if(root.left!=null && root.right==null) return ldep+1;
else mindep=Math.min(ldep,rdep);
return mindep+1;
N叉树的最大深度
例题559:给定一个 N 叉树,找到其最大深度。
最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。
N 叉树输入按层序遍历序列化表示,每组子节点由空值分隔(请参见示例)。
class Solution {
public int maxDepth(Node root) {
/*
//迭代层序遍历
int maxdep=0;
if(root==null) return maxdep;
Queue<Node> que=new LinkedList<Node>();
if(root!=null) que.add(root);
while(!que.isEmpty()){
int len=que.size();
maxdep++;
while(len>0){
Node curNode=que.poll();
if(curNode.children!=null){
for(int i=0;i<curNode.children.size();i++){
que.add(curNode.children.get(i));
}
len--;
}
}
}
return maxdep;
*/
//递归
return getDepth(root);
}
public int getDepth(Node root)
{
int dep=0;
if(root==null) return dep;
if(root.children!=null){
for(int i=0;i<root.children.size();i++){
dep=Math.max(dep,getDepth(root.children.get(i)));
}
}
return dep+1;
}
}
完全二叉树的节点个数
例题222:给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。
完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2h 个节点。
class Solution {
public int countNodes(TreeNode root) {
/*
//层序遍历
int count=0;
if(root==null) return count;
Queue<TreeNode> que=new LinkedList<TreeNode>();
if(root!=null) que.add(root);
while(!que.isEmpty()){
int len=que.size();
count+=len;
while(len>0){
TreeNode curNode=que.poll();
if(curNode.left!=null) que.add(curNode.left);
if(curNode.right!=null) que.add(curNode.right);
len--;
}
}
return count;
*/
return getNode(root);
}
//递归:和深度递归类似,分别找左右子树的节点数
public int getNode(TreeNode root){
if(root==null) return 0;
int lsum=getNode(root.left);
int rsum=getNode(root.right);
return lsum+rsum+1;
}
}
平衡二叉树
例题111:给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
下图给出二叉树中深度与高度的区别:
求深度适合用前序遍历,求高度适合用后序遍历;
迭代法也可以做但时间复杂度较高,回溯法就是最复杂的递归,一般不用迭代实现递归。
class Solution {
public boolean isBalanced(TreeNode root) {
int dep=getHeight(root);
if(dep!=-1) return true;//以-1表示不是平衡二叉树
else return false;
}
public int getHeight(TreeNode root){
if(root==null) return 0;
int lheight=getHeight(root.left);
if(lheight==-1) return -1;
int rheight=getHeight(root.right);
if(rheight==-1) return -1;
if(Math.abs(lheight-rheight)>1) return -1;
return Math.max(lheight,rheight)+1;
}
}
二叉树的所有路径
例题257:给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。
叶子节点 是指没有子节点的节点。
这是首道涉及到回溯的题目,因为需要把路径记录下来再回溯退回一个路径进入另一个路径。
图片可以看出,递归和回溯是一一对应的,有一个递归就有一个回溯。
递归三要素:
1.确定返回值类型和参数:在前序遍历中记录路径
viod Traversal(TreeNode root,List<Integer> path,List<String> res)
2.确定终止条件:找到叶子节点时就应该回溯
if(cur.left==null && cur.right==null)
{
终止逻辑
}
3.确定单层逻辑:采用前序遍历,每次递归就有一次回溯
if(cur.left){
遍历;
回溯;
}
if(cur.right){
遍历;
回溯;
}
完整代码如下:
//递归+回溯法
class Solution {
List<String> res=new ArrayList<>();
List<Integer> path=new ArrayList<>();
public List<String> binaryTreePaths(TreeNode root){
//递归找所有路径
TreePaths(root,path,res);
return res;
}
public void TreePaths(TreeNode root,List<Integer> path,List<String> res){
path.add(root.val);//中
//当前路径结束条件:找到该路径上的叶子节点,将当前路径转换为字符串保存
if(root.left==null && root.right==null){
StringBuilder p=new StringBuilder();
for(int i=0;i<path.size()-1;i++){
p.append(path.get(i));
p.append("->");
}
p.append(path.get(path.size()-1));//加入最后一位
res.add(p.toString());//转换为字符串
return;
}
if(root.left!=null){
TreePaths(root.left,path,res);//遍历
path.remove(path.size()-1);//回溯
}
if(root.right!=null){
TreePaths(root.right,path,res);//遍历
path.remove(path.size()-1);//回溯
}
}
}
1.Java可变字符串StringBuilder;
2.Java字符串拼接:+号的原理是在底层new一个StringBuilder,+号不能用在循环结构里面,不然每个循环都会new新对象,影响效率。因此,拼接字符串尽量用append方法。