目录
前言:
前面说过树是通过递归定义的。做二叉树的题,递归是避不开的。做递归题的思路就是终止条件+“公式”(我的个人理解,可能不是很严谨),只要准确找到终止条件和“公式”即可。
一.相同的树
根据两个根节点判断两棵树是否相同,对应题目:LeetCode:100. 相同的树。
思路:先看根节点是不是一样的,如果一样,就看结构是否一样(二叉树左右是有顺序的),即左子树是否一样,右子树是否一样(递归)
/**
* 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 boolean isSameTree(TreeNode p, TreeNode q) {
//两个节点都是空,是一样的,这也是终止条件
if(p==null && q==null){
return true;
}
//下面两个都属于节点不同
if(p==null && q!=null || p!=null && q==null){
return false;
}
if(p.val!=q.val){
return false;
}
//如果都相同,就看左子树和右子树
return isSameTree(p.left,q.left) && isSameTree(p.right,q.right);
}
}
终止条件就是上面的三个if。三个if,三种情况可以停下的条件。isSameTree(p.left,q.left) && isSameTree(p.right,q.right) 就是我所说的“公式”。
拓展:LeetCode:572. 另一棵树的子树
题目描述:给你两棵二叉树 root
和 subRoot
。检验 root
中是否包含和 subRoot
具有相同结构和节点值的子树。如果存在,返回 true
;否则,返回 false
。
思路:有了上个题的铺垫这个题就轻易很多。我们只需要一个一个节点的遍历,到一个节点就判断它和subRoot是不是同一颗树即可。
当然,我们还要得出终止条件。节点如果到叶节点的左右节点了,即root==null时,可以返回了。因为此时root怎么可能与subRoot对上(原题说个subRoot不为空),return false即可。
下面就要罗列“公式”了,首先判断root是不是和subRoot是一棵树,如果不是,再看root的左子树是不是与subRoot一样,再看root的右子树是不是与subRoot一样。如果都不一样,那么就是没有子树与subRoot一样,返回false。
整理一下:
1.判断root是不是和subRoot是一棵树
2.root的左子树是不是与subRoot一样
3.root的右子树是不是与subRoot一样
这样是不是清晰一些,能直接感受到“公式”的罗列。
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
if(root==null){
return false;
}
if(isSameTree(root,subRoot)){
return true;
}
if(isSubtree(root.left,subRoot)){
return true;
}
if(isSubtree(root.right,subRoot)){
return true;
}
return false;
}
二.翻转二叉树
对应题目:LeetCode:226. 翻转二叉树
这个题的思路很清晰:
1.交换root 的左右子树
2.交换root 左子树的左右子树
3.交换root 右子树的左右子树
终止条件就是“到底了”,root==null 了。
class Solution {
public TreeNode invertTree(TreeNode root) {
if(root==null){
return null;
}
TreeNode tem=root.left;
root.left=root.right;
root.right=tem;
invertTree(root.left);
invertTree(root.right);
return root;
}
}
可以看到,方法内容就是上面罗列的条件和“公式”。先终止条件,再1、2、3,最后return。
三.平衡二叉树
对应题目:LeetCode:110. 平衡二叉树
何为平衡二叉树?该树所有节点的左右子树的深度相差不超过1。
思路:先判断当前节点的左右子树是否深度相差不超过1,如果不超过,紧接着判断当前节点的左子树的左右子树的深度相差不超过1,当前结点的右子树的左右子树的深度相差不超过1。
class Solution {
public boolean isBalanced(TreeNode root) {
if(root==null){
return true;
}
if(root.left==null && root.right==null){
return true;
}
if(Math.abs(getHeight(root.left)-getHeight(root.right))<2){
return isBalanced(root.left)&& isBalanced(root.right);
}else{
return false;
}
}
public int getHeight(TreeNode root) {
if(root == null) {
return 0;
}
int leftHeight = getHeight(root.left);
int rightHeight = getHeight(root.right);
return leftHeight >rightHeight
? leftHeight+1 : rightHeight+1;
}
}
四.对称二叉树
对应题目:LeetCode:101. 对称二叉树
root的左节点要和右节点对称;左节点要和右节点对称,要左节点的左节点与右节点的右节点对称,左节点的右节点和右节点的左节点对称。
步骤:
1.先判断是否为空
2.如果不为空,判断是否一方为空,一方不为空
3.节点值是否一样,如果一样,再看左右子树
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root==null){
return true;
}
return isSymmetricChild(root.left,root.right);
}
public boolean isSymmetricChild(TreeNode rl,TreeNode rr){
if(rl==null && rr==null){
return true;
}
if(rl==null && rr!=null || rl!=null && rr==null){
return false;
}
if(rl.val==rr.val){
return isSymmetricChild(rl.left,rr.right) && isSymmetricChild(rl.right,rr.left);
}else{
return false;
}
}
}
五.根据前(后)和中序排序构建二叉树
这种问题在选择题遇到的比较多,但是也可以通过代码实现。
1.前序和中序构建
对应题目:LeetCode:105. 从前序与中序遍历序列构造二叉树
首先先弄明白前序和中序数组的目的。前序可以确定根节点,中序可以根据根节点来判断左右子树部分,前序继续判断左右子树的根节点,中序继续分左右树。
用一个 i 来遍历前序数组,写一个 find 方法目的是为了在中序数组中找到当前root的位置,找到root后,root的左面那一部分数组就是root的左子树的内容,右面的同理。在根据前序数组的遍历,完成构建。
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
int inEnd = inorder.length-1;
return buildTreeChild(preorder,0,inorder,inEnd);
}
public int size=0; //用于存储前序数组遍历到的位置
public TreeNode buildTreeChild(int[] preorder, int inBegin,int[] inorder,int inEnd){
if(size<preorder.length){
//如果size没有超出范围
//先new新节点
TreeNode cur=new TreeNode(preorder[size]);
size++;
//新节点的左孩子
if(inBegin>find(inorder,cur.val)-1){
cur.left=null;
}else{
cur.left=buildTreeChild(preorder,inBegin,inorder,find(inorder,cur.val)-1);
}
//新节点的右孩子
if(find(inorder,cur.val)+1>inEnd){
cur.right=null;
}else{
cur.right=buildTreeChild(preorder,find(inorder,cur.val)+1,inorder,inEnd);
}
return cur;
}else{
return null;
}
}
public int find(int[] inorder,int x){
for(int i=0;i<inorder.length;i++){
if(inorder[i] == x){
return i;
}
}
return 0;
}
}
2.后序和中序构建
对应题目:LeetCode:106. 从中序与后序遍历序列构造二叉树
思路与前序和中序构建相同,只不过是倒着遍历后序的数组(因为根在后面),先确定右子树,再确定左子树(因为倒着遍历先遍历到右节点)。
class Solution {
public int size; //这里的size要主动获取
public TreeNode buildTree(int[] inorder, int[] postorder) {
int inEnd=inorder.length-1;
size=postorder.length-1;
return buildTreeChild(inorder,0,postorder,inEnd);
}
public TreeNode buildTreeChild(int[] inorder, int inBegin,int[] postorder,int inEnd){
if(size>=0){
TreeNode cur=new TreeNode(postorder[size]);
size--; //注意是--
//确定右子树
if(find(inorder,cur.val)+1>inEnd){
cur.right=null;
}else{
cur.right=buildTreeChild(inorder,find(inorder,cur.val)+1,postorder,inEnd);
}
//确定左子树
if(inBegin>find(inorder,cur.val)-1){
cur.left=null;
}else{
cur.left=buildTreeChild(inorder,inBegin,postorder,find(inorder,cur.val)-1);
}
return cur;
}else{
return null;
}
}
public int find(int[] inorder,int x){
for(int i=0;i<inorder.length;i++){
if(inorder[i] == x){
return i;
}
}
return 0;
}
}
六.二叉树的层序遍历
对应题目:LeetCode:102. 二叉树的层序遍历
层序遍历用到了队列(Queue)。首先,获取队列里元素个数;然后,依次取出,并放入链表中;最后将取出的元素的左右节点(不为空)放入队列完成循环。
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> list=new LinkedList<>();
if(root==null){
return list;
}
Queue<TreeNode> queue=new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
int size=queue.size(); //获取元素数量
List<Integer> tem=new LinkedList<>();
while(size!=0){
size--;
TreeNode cur=queue.poll(); //取出元素
tem.add(cur.val); //加进链表
//将左节点放入队列
if(cur.left!=null){
queue.offer(cur.left);
}
//将右节点放入队列
if(cur.right!=null){
queue.offer(cur.right);
}
}
list.add(tem);
}
return list;
}
}
七.二叉树非递归遍历
前中后序非递归遍历都用到了栈(stack)。根据不同的需求入栈出栈以达到效果。
1.前序遍历
首先,root入栈,如果root有左节点,左节点继续入栈,不断循环左节点入栈,并且不断将左节点值加入链表中,直到找不到左节点位置。找不到左节点,往回走,看栈顶元素有没有右节点,如果有,右节点入栈。
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list = new LinkedList<>();
if(root==null){
return list;
}
Stack<TreeNode> stack=new Stack<>();
TreeNode cur = root;
while(cur!=null || !stack.empty()){
while(cur!=null){
stack.push(cur);
list.add(cur.val);
cur=cur.left;
}
if(!stack.empty()){
TreeNode top=stack.pop();
cur=top.right;
}
}
return list;
}
}
2.中序遍历
代码没什么太大变化,仅是 list.add(top.val); 这行换了个位置。这里是一直将左节点放入栈中的但是不放入链表中,直到某一节点没有左节点结束。这时从栈顶取出元素放入链表。
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> list=new LinkedList<>();
Stack<TreeNode> stack=new Stack<>();
TreeNode cur=root;
while(cur!=null || !stack.empty()){
while(cur!=null){
stack.push(cur);
cur=cur.left;
}
if(!stack.empty()){
TreeNode top=stack.pop();
list.add(top.val);
cur=top.right;
}
}
return list;
}
}
3.后序遍历
后序又与前两个不同,用前两个的思路,行不通,无法阻止根放入链表。
这时要再定义一个节点记录右边被没被放入链表。
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list=new LinkedList<>();
if(root == null){
return list;
}
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
TreeNode prev = null;
while (cur != null || !stack.isEmpty()) {
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
TreeNode top = stack.peek();
if (top.right == null || top.right == prev) {
list.add(top.val);
stack.pop();
prev = top;
} else {
cur = top.right;
}
}
return list;
}
}
八.总结
通过上述的题目可以看到,递归思想在做这类题时很重要。如果实在是看不明白就手动模拟看一下。其实做了一定量的递归类型的题目后根本就不用模拟了,直接条件+公式就可以做出来。用来递归的那个方法,本身就是用来求取某种情况的。如:翻转的方法,这个方法本身就是反转某一个树用的。这里可以这样看,题目让我们翻转的树,其实就是某一个大树的子树。这样是不是更容易理解,我们写的方法,可以理解为递归过程中子树的一个方法。在方法中用本身的方法去求其子树的方法,这不又圆回来了。
总之,实在不理解就多练,多练就会了。
标签:例题,Java,cur,return,二叉树,TreeNode,null,root,节点 From: https://blog.csdn.net/lllsure/article/details/140502493