剑指offer——|二叉树|Java题解
序号 | 题目 | 解题要点 |
---|---|---|
剑6 | 重建二叉树 | 二叉树,递归 |
剑8 | 树的子结构 | 代码的鲁棒性 |
剑19 | 二叉树的镜像 | 二叉树,递归 |
剑23 | 从上往下打印二叉树 | 二叉树,递归, 举例让抽象具体化 |
剑24 | 二叉搜索树的后序遍历 | 二叉树,递归, 举例让抽象具体化 |
剑25 | 二叉树中和为某一值的路径 | 二叉树,回溯,递归 |
剑39.1 | 二叉树的深度 | 递归 |
剑39.2 | 平衡二叉树 | 递归 |
剑58 | 二叉树的下一个节点 | 迭代,分情况讨论 |
剑59 | 对称的二叉树 | 递归 |
剑61 | 按s型打印二叉树 | 二叉树,栈 |
剑63 | 二叉搜索树的第K个结点 | 递归 |
剑6 重建二叉树
题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
题解:
-
根据前序遍历的特点,我们知道根结点为1
-
观察中序遍历。其中root节点G左侧的472必然是root的左子树,G右侧的5386必然是root的右子树。
-
观察左子树472,左子树的中的根节点必然是大树的root的leftchild。在前序遍历中,大树的root的leftchild位于root之后,所以左子树的根节点为2。
-
同样的道理,root的右子树节点5386中的根节点也可以通过前序遍历求得。在前序遍历中,一定是先把root和root的所有左子树节点遍历完之后才会遍历右子树,并且遍历的左子树的第一个节点就是左子树的根节点。同理,遍历的右子树的第一个节点就是右子树的根节点。
-
观察发现,上面的过程是递归的。先找到当前树的根节点,然后划分为左子树,右子树,然后进入左子树重复上面的过程,然后进入右子树重复上面的过程。最后就可以还原一棵树了。
该步递归的过程可以简洁表达如下:
- 确定根,确定左子树,确定右子树。2. 在左子树中递归。3. 在右子树中递归。 4.打印当前根。
/**
* Definition for binary tree
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
public class Solution {
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
return reConstruct(pre,0,pre.length-1,in,0,in.length-1);
}
public static TreeNode reConstruct(int[] pre,int preleft, int preright, int[] in,int inleft,int inright){
if(preleft > preright || inleft > inright){
return null;
}
TreeNode root = new TreeNode(pre[preleft]);
for(int i =inleft; i < in.length; i++){
if(in[i] == pre[preleft]){
root.left = reConstruct(pre,preleft+1,preleft+i-inleft,in,inleft,i-1);
root.right = reConstruct(pre,preleft+i-inleft+1,preright,in,i+1,inright);
}
}
return root;
}
}
剑18 树的子结构
题目:输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
要查找树A中是否存在和树B结构一样的子树,我们可以分成两步:第一步在树A中找到和B的根结点的值一样的结点R,第二步再判断树A以R为根结点的子树是不是包含和树B一样的结构。
题解:第一步在树A中查找与根结点的值一样的结点R,实际上就是树的遍历。
第二步是判断树A中以R为根结点的子树是不是和树B具有相同的结构。同样,我们也可以用递归的思路来考虑:如果结点R的值和树B的根结点不同,则以R为根结点的子树和树B一定不具有相同的结点;如果他们的值相同,则递归地判断它们各自的左右结点的值是不是相同。递归的终止条件是我们达到了树A或者树B的叶结点。
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
boolean result = false;
一定要注意边界条件的检查,即检查空指针。否则程序容易崩溃,面试时尤其要注意。这里当Tree1和Tree2都不为零的时候,才进行比较。否则直接返回false
if(root1 != null & root2 != null){
//如果找到了对应Tree2的根节点的点
if(root1.val == root2.val){
//以这个根节点为为起点判断是否包含Tree2
result = sameTree(root1,root2);
}
//如果找不到,那么就再去root的左儿子当作起点,去判断是否包含Tree2
if(!result){
result = HasSubtree(root1.left,root2);
}
//如果找不到,那么就再去root的右儿子当作起点,去判断是否包含Tree2
if(!result){
result = HasSubtree(root1.right,root2);
}
}
return result;
}
public static boolean sameTree(TreeNode root1,TreeNode root2){
//如果Tree2已经遍历完了都能对应的上,返回true
if(root2==null){
return true;
}
//如果Tree2还没有遍历完,Tree1却遍历完了。返回false
if(root1==null){
return false;
}
if(root1.val != root2.val)
return false;
return sameTree(root1.left,root2.left) && sameTree(root1.right, root2.right);
}
}
剑19 二叉树的镜像
题目:操作给定的二叉树,将其变换为源二叉树的镜像。
题解:这是典型的递归求解题目。递归过程为:
- 判断根节点是否为空
- 交换根节点的左右子树
- 对左子树进行镜像操作
- 对右子树进行镜像操作
public class Solution {
public void Mirror(TreeNode root) {
if(root ==null)
return;
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
if(root.left !=null){
Mirror(root.left);
}
if(root.right != null){
Mirror(root.right);
}
}
}
剑24.二叉搜索树的后序遍历序列
题目描述
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
题解:二叉搜索树的根节点的值大于左子树的值,并且小于右子树的值。利用这个性质我们进行递归判断。因为题目给的是后序遍历,那么数组的最后一个值为根节点的值。递归过程如下:
- 取出根节点的值和数组中的值比较,当有数字大于根节点时,跳出循环,此时记录的位置就是左子树的位置。
- 然后再判断右子树的值是否都大于根节点,如果不是则返回false。
- 判断左子树的后序遍历是否为二叉搜索树
- 判断右子树的后序遍历是否为二叉搜索树
在第三步和第四步需要注意边界范围。
import java.util.Arrays;
public class Solution {
public boolean VerifySquenceOfBST(int [] sequence) {
int len = sequence.length;
if(len == 0){
return false;
}
int root = sequence[len-1];
int i = 0;
//注意边界范围,右子树的后序遍历不包括根节点(数组的最后一个数)
for(;i<len-1;i++){
if(sequence[i] > root){
break;
}
}
//注意边界范围,右子树的后序遍历不包括根节点(数组的最后一个数)
for(int j = i;j<len-1;j++){
if(sequence[j] < root){
return false;
}
}
boolean left = true;
boolean right = true;
//必须有i>0,也就是i>=1。一个节点的情况,此时len为1,i为0,不进入if判断,直接返回
if(i>0){
left = VerifySquenceOfBST(Arrays.copyOfRange(sequence,0,i));
}
//注意边界范围,右子树的后序遍历不包括根节点(数组的最后一个数)
if(i<len-1){
right = VerifySquenceOfBST(Arrays.copyOfRange(sequence,i,len-1));
}
return left && right;
}
}
剑25.二叉树中和为某一值的路径
题目描述:输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)
题解:使用回溯求解。解决一个回溯问题,实际上就是一个决策树的遍历过程。需要思考 3 个问题:
- 路径:也就是已经做出的选择。
- 选择列表:也就是你当前可以做的选择。
- 结束条件:也就是到达决策树底层,无法再做选择的条件。
回溯算法的框架:
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择
其核心就是 for 循环里面的递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」。在套用框架时,我们着重需要思考的是选择列表是什么。
本题的结束条件是sum==0和左右子树为空
import java.util.ArrayList;
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
private ArrayList<ArrayList<Integer>> result = new ArrayList<>();
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
ArrayList<Integer> track = new ArrayList<>();
backtrack(track,target,root);
return result;
}
public void backtrack(ArrayList<Integer> track, int sum, TreeNode root){
if(root == null)
return;
//使用减法,先减去root的值
sum -= root.val;
//做选择,在这里做选择的原因是题目要求路径必须经过根节点
track.add(root.val);
//结束条件
if(sum == 0 && root.left == null && root.right ==null){
result.add(new ArrayList<>(track));
}
//递归,左子树为选择列表
backtrack(track,sum,root.left);
//递归,右子树为选择列表
backtrack(track,sum,root.right);
track.remove(track.size()-1);
}
}
剑39.1 二叉树的深度
题目描述:输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
public class Solution {
public int TreeDepth(TreeNode root) {
if(root == null)
return 0;
int left = TreeDepth(root.left)+1;;
int right = TreeDepth(root.right)+1;
return Math.max(left,right);
}
}
剑39.2 平衡二叉树
题目描述:输入一棵二叉树,判断该二叉树是否是平衡二叉树。
题解:有了求二叉树的深度的经验之后,我们就很容易想到一个思路:在遍历树的每个结点的时候,调用函数TreeDepth得到它的左右子树的深度。如果每个结点的左右子树的深度相差都不超过1,按照定义它就是一颗平衡的二叉树。
public class Solution {
public boolean IsBalanced_Solution(TreeNode root) {
if(root == null)
return true;
int leftDepth = TreeDepth(root.left);
int rightDepth = TreeDepth(root.right);
if((rightDepth-leftDepth) > 1 || (rightDepth-leftDepth) < -1)
return false;
return IsBalanced_Solution(root.left) && IsBalanced_Solution(root.right);
}
public int TreeDepth(TreeNode root){
if(root == null)
return 0;
int left = TreeDepth(root.left)+1;
int right = TreeDepth(root.right)+1;
return Math.max(left,right);
}
}
剑58 二叉树的下一个结点
题目描述:给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
题解:二叉树的下一个节点可分成两大类:
- 有右子树的,那么下个结点就是右子树最左边的点;
- 没有右子树的,也可以分成两类,
a)是父节点左孩子,那么父节点就是下一个节点 ;
b)是父节点的右孩子,找他的父节点的父节点的父节点…直到当前结点是其父节点的左孩子位置。如下图。如果没有,那么他就是尾节点,返回空节点。
/*
public class TreeLinkNode {
int val;
TreeLinkNode left = null;
TreeLinkNode right = null;
TreeLinkNode next = null;
TreeLinkNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public TreeLinkNode GetNext(TreeLinkNode pNode){
if(pNode == null){
return null;
}
if(pNode.right!= null){
pNode = pNode.right;
while(pNode.left != null){
pNode = pNode.left;
}
return pNode;
}
while(pNode.next != null){
if(pNode.next.left == pNode){
return pNode.next;
}
pNode = pNode.next;
}
return null;
}
}
剑59 对称的二叉树
题目描述:请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
public class Solution {
boolean isSymmetrical(TreeNode pRoot)
{
return isSymmetrical(pRoot, pRoot);
}
boolean isSymmetrical(TreeNode pRoot1, TreeNode pRoot2){
if(pRoot1 == null && pRoot2 ==null)
return true;
if(pRoot1 ==null || pRoot2 == null)
return false;
if(pRoot1.val ==pRoot2.val){
return isSymmetrical(pRoot1.left,pRoot2.right)&&
isSymmetrical(pRoot1.right,pRoot2.left);
}else
return false;
}
}
剑61.按S型打印二叉树
题目描述:请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
题解:此题解题思路不难。设置两个栈,一个栈从右到左入栈,从左到右出栈;另一个栈从左到右入栈,从右到左出栈。首先把根节点push入栈1,再进行每行的入栈出栈,注意入栈出栈顺序和空节点的判断。
import java.util.ArrayList;
import java.util.*;
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer>> result = new ArrayList<>();
if(pRoot == null){
return result;
}
Stack<TreeNode> s1 = new Stack<>();
Stack<TreeNode> s2 = new Stack<>();
s1.push(pRoot);
while(!s1.isEmpty() || !s2.isEmpty()){
if(!s1.isEmpty()){
ArrayList<Integer> list = new ArrayList<>();
while(!s1.isEmpty()){
TreeNode node = s1.pop();
list.add(node.val);
if(node.left != null){
s2.push(node.left);
}
if(node.right != null){
s2.push(node.right);
}
}
result.add(list);
}
else{
ArrayList<Integer> list = new ArrayList<>();
while(!s2.isEmpty()){
TreeNode node = s2.pop();
list.add(node.val);
if(node.right != null){
s1.push(node.right);
}
if(node.left != null){
s1.push(node.left);
}
}
result.add(list);
}
}
return result;
}
}
剑63.二叉搜索树的第k个节点
题目描述:给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。
题解:如果按照中序遍历的顺序遍历一颗二叉搜索树,遍历序列的数值是递增排序的,只需要用中序遍历算法遍历一颗二叉搜索树,就很容易找出它的第K大的结点。
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
int index = 0;
TreeNode KthNode(TreeNode pRoot, int k){
if(pRoot != null){
TreeNode node = KthNode(pRoot.left,k);
if(node != null){
return node;
}
index++;
if(index == k){
return pRoot;
}
node = KthNode(pRoot.right,k);
if(node != null){
return node;
}
}
return null;
}
}