Title: 剑指offer|解题代码-2-Tree
Slug:20190926-Tree
Date: 2019-09-26 17:40
Category: interview
Tags:algorithm
Summary: 剑指offer解题代码-2-Tree(Java)
目录:
Tree
- 004-重建二叉树 √
- 017-树的子结构 √
- 018-二叉树的镜像 √
- 022-从上往下打印二叉树 (二叉树按层遍历)√
- 023-二叉搜索树的后序遍历序列 √
- 024-二叉树中和为某一值的路径 √
- 026-二叉搜索树与双向链表 √
- 038-二叉树的深度 √
- 039-平衡二叉树 √
- 057-二叉树的下一个结点 ** √
- 058-对称的二叉树 √
- 059-按之字形顺序打印二叉树 √
- 060-把二叉树打印成多行 (层序遍历,逐层打印) √
- 061-序列化二叉树 √
- 062-二叉搜索树的第k个结点 √
Tree
004-重建二叉树 √
2019-3-6 13:09 --- 2019-3-6 13:24
题目描述:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
分析:
- 前序遍历的第一个元素为树的根节点 root, 后面n个左子树元素,m个右子树元素
- 中序遍历中 root结点的左边元素为所有左子树元素共n个, 右边元素为所有右子树元素共m个
思路:递归方法
- 前序遍历第一个元素为 root
- 中序遍历找到root的index, [:index]为左子树的中序遍历, [index+1:]为右子树中序遍历
- 前序遍历中 [1:index+1]为左子树的前序遍历,[index+1:]为右子树的前序遍历
- 递归 传入左子树和右子树的前序/中序遍历
- 递归终止条件: 前序/中序遍历为空 return None
解题代码:(java)
/**
* Definition for binary tree
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
import java.util.Arrays;
public class Solution {
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
if(pre.length == 0 || in.length == 0)
return null;
TreeNode root = new TreeNode(pre[0]);
int index = 0;
for(int i=0;i<in.length;i++){
if(pre[0] == in[i]){
index = i;
break;
}
}
int [] leftPre = Arrays.copyOfRange(pre,1,index+1);
int [] leftIn = Arrays.copyOfRange(in,0,index);
int [] rightPre = Arrays.copyOfRange(pre,index+1,pre.length);
int [] rightIn = Arrays.copyOfRange(in,index+1,in.length);
root.left = reConstructBinaryTree(leftPre,leftIn);
root.right = reConstructBinaryTree(rightPre,rightIn);
return root;
}
}
解题代码:(Python)
# -*- coding:utf-8 -*-
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
# 返回构造的TreeNode根节点
def reConstructBinaryTree(self, pre, tin):
if not pre or not tin:
return None
root = TreeNode(pre[0])
index = tin.index(pre[0])
root.left = self.reConstructBinaryTree(pre[1:index+1],tin[:index])
root.right = self.reConstructBinaryTree(pre[index+1:],tin[index+1:])
return root
017-树的子结构 √
题目描述 : 输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
思路:
- 遍历树A,找到值与树B root值相同的结点
- 执行判断函数,检查左右孩子结点是否相同
解题代码:(Java)
/**
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;
if(root1!=null && root2!=null){
if(root1.val == root2.val)
result = isTree1HasTree2(root1,root2);
if(result !=true)
result = HasSubtree(root1.left,root2);
if(result !=true)
result = HasSubtree(root1.right,root2);
}
return result;
}
public boolean isTree1HasTree2(TreeNode root1,TreeNode root2){
if(root2==null)
return true;
if(root1==null)
return false;
if(root1.val != root2.val)
return false;
return isTree1HasTree2(root1.left,root2.left) && isTree1HasTree2(root1.right,root2.right);
}
}
018-二叉树的镜像 √
题目描述 : 操作给定的二叉树,将其变换为源二叉树的镜像。
思路: 递归遍历二叉树,交换每一个节点的左右孩子
解题代码:(Java)
public class Solution {
public void Mirror(TreeNode root) {
if(root != null){
// swap left and right child
TreeNode temNode = root.left;
root.left = root.right;
root.right = temNode;
if(root.left!=null)
Mirror(root.left);
if(root.right!=null)
Mirror(root.right);
}
}
}
022-从上往下打印二叉树 (二叉树按层遍历)√
题目描述 : 从上往下打印出二叉树的每个节点,同层节点从左至右打印。
思路:
- Queue 将 root,root.left,root.right 依次加入Queue
补充知识: Java 集合 - LinkedList vs Queue
- import java.util.LinkedList;
- LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
- queue.isEmpty(); // 判断是否为空
- queue.offer();//将节点入队 (Push)
- queue.poll();//队头元素出队并返回 (Pop)
解题代码:(Java)
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
ArrayList<Integer> resList = new ArrayList<Integer>();
Queue<TreeNode> queue = new LinkedList<TreeNode>();
// debug: new LinkedList<TreeNode>() <>后的括号()漏了!注意细节。
if(root == null)
return resList;
TreeNode curNode = null;
queue.offer(root);
while(!queue.isEmpty()){
curNode = queue.poll();
if(curNode.left != null){
queue.offer(curNode.left);
}
if(curNode.right != null){
queue.offer(curNode.right);
}
resList.add(curNode.val);
}
return resList;
}
}
023-二叉搜索树的后序遍历序列 √
题目描述 : 输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
补充知识 1 : 二叉搜索树(Binary Search Tree) or 排序二叉树(Sorted Binary Tree)
- 若任意节点的左子树不空,则左子树上所有节点的值均小于它的根节点的值;
- 若任意节点的右子树不空,则右子树上所有节点的值均大于它的根节点的值;
- 任意节点的左、右子树也分别为二叉查找树;
- 没有键值相等的节点。
补充知识 2 : 树的前中后序遍历,前中后是指根节点root被打印的顺序,后序意味着打印顺序为 左右根
例如: [5,7,6] 可以是 BST 5-6-7 的后序遍历,故返回Ture,然而并没有一棵BST的后序遍历能够为 [7,4,6,5]
分析后可以得知,BST 后序遍历的序列有如下特征:
- 最后一个节点为 root
- [0] - [len-2] 被分成两段,前一段为root的左孩子节点,值都小于root.val, 后一段为root 的右孩子节点,值都大于root.val
- 根据 BST 的特性,对左右孩子进行递归调用继续判断
解题代码:(Java)
// 问题主要在于:一直在 copy 数组,效率较低
import java.util.Arrays;
public class Solution {
public boolean VerifySquenceOfBST(int [] sequence) {
int len = sequence.length;
if(len <= 0 || sequence==null)
return false;
int rootVal = sequence[len-1];
int i = 0;
for(; i<len-1;i++){
if(sequence[i]>rootVal){
break;
}
}
int index = i;
for (int j = i;j<len-1;j++){
if(sequence[j]<rootVal)
return false;
}
boolean left=true;
boolean right=true;
if(index>0){
int [] leftNodes = Arrays.copyOfRange(sequence,0,index);
left = VerifySquenceOfBST(leftNodes);
}
if(index<len-1){
int [] rightNodes = Arrays.copyOfRange(sequence,index,len-1);
right = VerifySquenceOfBST(rightNodes);
}
return (left&&right);
}
}
024-二叉树中和为某一值的路径 √
20190924 总结: 这题前前后后刷了差不多2个小时
题目描述 : 输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)
思路:
- 每次都从根节点出发,需要一个栈结构记录路径 path,方便回溯,一个 int currentVal 记录当前路径节点值之和
- 当 currentVal == targetVal 且 currentNode 是叶子节点时,输出路径path (逆序)
- 当有孩子节点时递归调用 FindPath方法
- 注意: 如果当前节点为叶子节点但 currentVal != targetVal 则需要把 currentVal - currentNode.val 且 从路径中删除当前节点,然后返回父节点
import java.util.ArrayList;
import java.util.Stack;
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
static ArrayList<ArrayList<Integer>> res = new ArrayList<>();
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
if(root == null)
return null;
Stack <Integer> path = new Stack();
int currentVal = 0;
FindPath(root,target,path,currentVal);
System.out.println(res);
return res;
}
public void FindPath(TreeNode root, int target, Stack<Integer> path, int currentVal){
path.push(root.val);
currentVal += root.val;
boolean isLeaf = (root.left == null && root.right == null);
if (currentVal == target && isLeaf){
ArrayList<Integer> oneRes = new ArrayList<>();
while(!path.isEmpty()){
oneRes.add(0,path.pop());
}
res.add(oneRes);
}
// if no new path 2, leftNodes will influence rightNodes
Stack <Integer> path2 = new Stack();
path2.addAll(path);
if(root.left!=null)
FindPath(root.left,target,path,currentVal);
if(root.right!=null)
FindPath(root.right,target,path2,currentVal);
// if can not find the path, delete the current node from the path and add
if(!path.isEmpty())
path.pop();
currentVal -= root.val;
}
}
解题代码2(Java)
public class Solution {
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
ArrayList<ArrayList<Integer>> paths=new ArrayList<ArrayList<Integer>>();
if(root==null)return paths;
find(paths,new ArrayList<Integer>(),root,target);
return paths;
}
public void find(ArrayList<ArrayList<Integer>> paths,ArrayList<Integer> path,TreeNode root,int target){
path.add(root.val);
if(root.left==null&&root.right==null){
if(target==root.val){
paths.add(path);
}
return;
}
ArrayList<Integer> path2=new ArrayList<>();
path2.addAll(path);
if(root.left!=null)find(paths,path,root.left,target-root.val);
if(root.right!=null)find(paths,path2,root.right,target-root.val);
}
}
026-二叉搜索树与双向链表 √
题目描述 : 输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
思路:
- 记录链表末尾节点 tail
- 中序遍历,连接 tail 和 pRoot
- 建议画图理解 else 内的3行连接代码
解题代码:(Java)
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
TreeNode root = null; // real root as return value
TreeNode tail = null; // the last node of LinkedList
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree == null)
return null;
if(pRootOfTree.left != null)
Convert(pRootOfTree.left);
if(root == null){
root = pRootOfTree;
tail = pRootOfTree;
}
else{ // Link
tail.right = pRootOfTree;
pRootOfTree.left = tail;
tail = pRootOfTree;
}
if(pRootOfTree.right != null)
Convert(pRootOfTree.right);
return root;
}
}
038-二叉树的深度 √
题目描述 : 输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
解题代码:(Java)
public class Solution {
public int TreeDepth(TreeNode root) {
if(root == null) return 0;
int left = TreeDepth(root.left);
int right = TreeDepth(root.right);
return Math.max(left,right)+1;
}
}
039-平衡二叉树 √
题目描述 : 输入一棵二叉树,判断该二叉树是否是平衡二叉树。
补充知识: 平衡二叉树
平衡二叉树是一棵 空树或它的 左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树
思路: 非常直白,只需要计算左右子树高度,并且判断高度差是否小于等于1
解题代码 1:(Java)
public class Solution {
public boolean IsBalanced_Solution(TreeNode root) {
return getDepth(root)!= -1;
}
public int getDepth(TreeNode root){
if (root == null) return 0;
int left = getDepth(root.left);
if(left == -1) return -1;
int right = getDepth(root.right);
if(right == -1) return -1;
return Math.abs(left-right)>1 ? -1 : Math.max(left,right)+1;
}
}
解题代码 2: (java)
import java.lang.Math;
public class Solution {
public boolean IsBalanced_Solution(TreeNode root) {
if(root == null)
return true;
int left = getDepth(root.left);
int right = getDepth(root.right);
return Math.abs(left-right)<=1;
}
public int getDepth(TreeNode root) {
if(root == null)
return 0;
int left = getDepth(root.left);
int right = getDepth(root.right);
return Math.max(left,right)+1;
}
}
057-二叉树的下一个结点 ** √
题目描述 : 给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
思路:
- 有右孩子,则为右子树的最左节点
- 无右孩子,则找父节点,直到当前节点为其父节点的左孩子时,返回父节点
解题代码:(Java)
/*
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 pNode;
// 有右孩子,则为右子树的最左节点
if(pNode.right != null)
return getLeftMost(pNode.right);
// 无右孩子,则找父节点,直到当前节点为其父节点的左孩子时,返回父节点
else {
TreeLinkNode parent = pNode.next;
while (parent != null && parent.left != pNode){
pNode = parent;
parent = pNode.next;
}
return parent;
}
}
public TreeLinkNode getLeftMost(TreeLinkNode node){
if(node == null) return node;
while (node.left != null){
node = node.left;
}
return node;
}
}
058-对称的二叉树 √
题目描述 : 请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
思路: 判断左子树和右子树对称
解题代码:(Java)
public class Solution {
boolean isSymmetrical(TreeNode pRoot)
{
if(pRoot == null) return true;
return isSymmetrical(pRoot.left,pRoot.right);
}
boolean isSymmetrical(TreeNode r1,TreeNode r2){
if(r1 == null && r2 == null)
return true;
if(r1 != null && r2 != null)
return r1.val == r2.val && isSymmetrical(r1.left,r2.right) && isSymmetrical(r1.right,r2.left);
return false;
}
}
059-按之字形顺序打印二叉树 √
思路:
- 层序遍历的思想 (队列)
- 左右打印顺序用一个boolean flag来控制
基于层序遍历按层输出的代码,加入reverse,并不需要两个stack
核心:
- preLayerLastNode 记录上一层最后的节点
- curLayerLastNode 记录当前层最后的节点 (当前层意味着正在将孩子节点加入队列,本题用linkedList实现)
解题代码:(Java)
public class Solution {
public ArrayList<ArrayList<Integer>> Print(TreeNode root) {
ArrayList<ArrayList<Integer>> resLists = new ArrayList<ArrayList<Integer>>();
if(root == null)
return resLists;
ArrayList<Integer> resList = new ArrayList<Integer>();
LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
TreeNode preLayerLastNode = root; // pre layer last node
TreeNode curLayerLastNode = root; // current layer last node
boolean leftToRight = true; // Flag using to control print order
queue.offer(root);
while(!queue.isEmpty()){
TreeNode curNode = queue.poll();
if(curNode.left != null){
queue.offer(curNode.left);
curLayerLastNode = queue.getLast();
}
if(curNode.right != null){
queue.offer(curNode.right);
curLayerLastNode = queue.getLast();
}
if(curNode == preLayerLastNode){ // add
// add resList to resLists and create a new resList
resList.add(curNode.val);
if(leftToRight)
resLists.add(resList);
else{
Collections.reverse(resList);
resLists.add(resList);
}
preLayerLastNode = curLayerLastNode;
resList = new ArrayList<Integer>();
leftToRight = !leftToRight;
}
else{
resList.add(curNode.val);
}
}
return resLists;
}
}
060-把二叉树打印成多行 (层序遍历,逐层打印) √
题目描述 : 从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
思路:层序遍历 + 层最后节点判定
参考:二叉树逐层遍历打印
解题代码:(java)
import java.util.ArrayList;
import java.util.LinkedList;
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
ArrayList<ArrayList<Integer> > Print(TreeNode root) {
ArrayList<ArrayList<Integer>> resLists = new ArrayList<ArrayList<Integer>>();
if(root == null)
return resLists;
ArrayList<Integer> resList = new ArrayList<Integer>();
LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
TreeNode preLayerLastNode = root; // pre layer last node
TreeNode curLayerLastNode = root; // current layer last node
queue.offer(root);
while(!queue.isEmpty()){
TreeNode curNode = queue.poll();
if(curNode.left != null){
queue.offer(curNode.left);
curLayerLastNode = queue.getLast();
}
if(curNode.right != null){
queue.offer(curNode.right);
curLayerLastNode = queue.getLast();
}
if(curNode == preLayerLastNode){
// add resList to resLists and create a new resList
resList.add(curNode.val);
resLists.add(resList);
preLayerLastNode = curLayerLastNode;
resList = new ArrayList<Integer>();
}
else{
resList.add(curNode.val);
}
}
return resLists;
}
}
061-序列化二叉树 √
题目描述 : 请实现两个函数,分别用来序列化和反序列化二叉树 二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。 二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。
思路:
- 序列化是前序遍历的变形,加入到一个String中
- 反序列化也是前序遍历的变形,利用一个LinkedList or Queue结构存储node vals 构造Tree
解题代码:(java)
import java.util.LinkedList;
import java.util.Queue;
public class Solution {
String Serialize(TreeNode root) {
if(root == null){
return "#!";
}
String res = root.val+"!";
res += Serialize(root.left);
res += Serialize(root.right);
return res;
}
TreeNode Deserialize(String str) {
String [] nodes = str.split("!");
Queue<String> queue = new LinkedList<String>();
for(int i = 0; i< nodes.length;i++){
queue.offer(nodes[i]);
}
return DeserialTreeByPreOrder(queue);
}
TreeNode DeserialTreeByPreOrder(Queue<String> queue) {
String node = queue.poll();
if(node.equals("#"))
return null;
TreeNode head = new TreeNode(Integer.valueOf(node));
head.left = DeserialTreeByPreOrder(queue);
head.right = DeserialTreeByPreOrder(queue);
return head;
}
}
062-二叉搜索树的第k个结点 √
题目描述: 给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8)中,按结点数值大小顺序第三小结点的值为4。
解题代码:
public class Solution {
int n = 0;
TreeNode res = null;
TreeNode KthNode(TreeNode pRoot, int k)
{
if(pRoot == null)
return null;
KthNode(pRoot.left,k);
n+=1;
if(n==k)
res = pRoot;
KthNode(pRoot.right,k);
return res;
}
}
解题代码2:(Java)
public class Solution {
int n = 0;
TreeNode res = null;
TreeNode KthNode(TreeNode pRoot, int k)
{
if(pRoot == null)
return null;
if (pRoot.left!=null){
KthNode(pRoot.left,k);
}
n++;
if (n==k){
res=pRoot;
}
if (pRoot.right!=null){
KthNode(pRoot.right,k);
}
return res;
}
}