上一章写了总结了一些链表的算法操作,另外,在实际中树尤其是二叉树的操作也是很重要的,接下来再总结一下。类名后边的数字是剑指offer书上的题号,题目都是在牛客网上AC的,但是它们的那个平台的方法入口名有些不规范。
/**
* 题目描述:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。
假设输入的数组的任意两个数字都互不相同。
注意:二叉搜索树后序遍历的特点
*/
public class VerifySquenceOfBST24 {
public boolean verifySquenceOfBST(int [] sequence) {
if (sequence == null || sequence.length <= 0) {
return false;
}
return verifyBST(sequence, 0, sequence.length - 1);
}
public boolean verifyBST(int [] sequence, int start, int end) {
// 如果start >= end 就说明没有左右子树了
if (start >= end) {
return true;
}
//后序遍历最后一个是根
int root = sequence[end];
//找左子树部分
int i = start;
for (; i < end; i++) {
if (sequence[i] > root) {
break;
}
}
// 右子树部分 如果右子树部分还有小于根的,就要直接返回false
int index = i;
for (i = index; i < end; i++) {
if (sequence[i] < root) {
return false;
}
}
// 再递归验证
return verifyBST(sequence, start, index - 1) && verifyBST(sequence, index, end - 1);
}
}
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
/**
* 输入一棵二叉树,求该树的深度。
* 从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
* 树的遍历 递归实现
*/
public class TreeDepth39 {
public int TreeDepth(TreeNode root) {
if (root == null) {
return 0;
}
return 1 + Math.max(TreeDepth(root.left), TreeDepth(root.right));
}
}
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
/**
* 序列化和反序列化树
* 1. 生成中序、前序遍历,然后实现反序列化
2. 前序遍历,但是将null用特殊符号来表示,从而实现反序列化 递归实现
*/
public class SerializeTree62 {
// index用来记录字符串序列化的位置
int index = -1;
// 前序遍历,null用'#'字符表示,并节点用','号分割
String Serialize(TreeNode root) {
StringBuilder sb = new StringBuilder();
if (root == null) {
sb.append("#,");
return sb.toString();
}
sb.append(root.val);
sb.append(",");
sb.append(Serialize(root.left));
sb.append(Serialize(root.right));
return sb.toString();
}
//反序列化,按照序列化的规则
TreeNode Deserialize(String str) {
index++;
TreeNode root = null;
String[] splits = str.split(",");
// 这个判断其实是递归退出条件
if (!"#".equals(splits[index])) {
root = new TreeNode(Integer.valueOf(splits[index]));
root.left = Deserialize(str);
root.right = Deserialize(str);
}
return root;
}
}
/**
* Definition for binary tree
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
/**
* 题目描述
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。
假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
* 前序遍历,第一个是根,然后把中序遍历分为两部分,再根据中序遍历的分的左子树、右子树部分,再把前序遍历分为两部分
从而又和原问题一样了,所以继续递归
*/
public class ReConstructBinaryTree06 {
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
if (pre == null || in == null || pre.length <= 0 || in.length <= 0 || pre.length != in.length) {
return null;
}
return constructBinaryTree(pre, in, 0, pre.length - 1, 0, in.length - 1);
}
// 关键问题是划分 前、中序遍历。找到相应的位置即可实现递归
public TreeNode constructBinaryTree(int [] pre,int [] in,int start1, int end1, int start2, int end2) {
if (start1 > end1 || start2 > end2) {
return null;
}
TreeNode root = new TreeNode(pre[start1]);
int indexIn = start2;
for (; indexIn <= end2; indexIn++) {
if (in[indexIn] == root.val) {
break;
}
}
int num = indexIn - start2;
root.left = constructBinaryTree(pre, in, start1 + 1, start1 + num, start2, indexIn - 1);
root.right = constructBinaryTree(pre, in, start1 + num + 1, end1, indexIn + 1, end2);
return root;
}
}
import java.util.*;
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
/**
* 题目描述:从上往下打印出二叉树的每个节点,同层节点从左至右打印。
* 层次遍历 队列
*/
public class PrintTreeFromTopToButtom23 {
public ArrayList printFromTopToBottom(TreeNode root) {
ArrayList list = new ArrayList();
if (root == null) {
return list;
}
Queue queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
list.add(node.val);
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
return list;
}
}
import java.util.*;
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
/**
* 之字形打印树,用两个栈来实现,第一个奇数行(首行为第一行)左右遍历放入第一个栈1,
* 然后遍历第二行时右左遍历放在栈2,用两个栈来实现,第一个奇数行(首行为第一行)左右遍历放入第一个栈1,
* 然后遍历第二行时右左遍历放在栈2
*/
public class PrintTreeByZhi61 {
public ArrayList > Print(TreeNode pRoot) {
ArrayList> list = new ArrayList>();
if (pRoot == null) {
return list;
}
LinkedList stack1 = new LinkedList<>();
LinkedList stack2 = new LinkedList<>();
int level = 1; //用来判断是左右遍历还是右左遍历
stack1.push(pRoot);
while(!stack1.isEmpty() || !stack2.isEmpty()) {
ArrayList nodeList = new ArrayList<>();
// 需要左右遍历stack1,子节点放入stack2
if ((level & 1) == 1) {
level++;
while(!stack1.isEmpty()) {
TreeNode node = stack1.pop();
nodeList.add(node.val);
if (node.left != null) {
stack2.push(node.left);
}
if (node.right != null) {
stack2.push(node.right);
}
}
} else {
level++;
while(!stack2.isEmpty()) {
TreeNode node = stack2.pop();
nodeList.add(node.val);
if (node.right != null) {
stack1.push(node.right);
}
if (node.left != null) {
stack1.push(node.left);
}
}
}
list.add(nodeList);
}
return list;
}
}
import java.util.*;
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
/**
* 树的层次遍历,用队列实现
* 第一步就要先把root入队
*/
public class PrintTree60 {
ArrayList > Print(TreeNode pRoot) {
ArrayList> list = new ArrayList<>();
if (pRoot == null) {
return list;
}
Queue queue = new LinkedList<>();
queue.offer(pRoot);
while (!queue.isEmpty()) {
ArrayList nodeVal = new ArrayList<>();
// 用一个数来判断此层需要打印的数目
int end = queue.size();
// 一次把这层所有的节点的值保存
while (end != 0) {
TreeNode node = queue.poll();
nodeVal.add(node.val);
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
end--;
}
list.add(nodeVal);
}
return list;
}
}
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
/**
* 二叉树镜像
* 递归实现
*/
public class Mirror19 {
public void mirror(TreeNode root) {
mirrorTree(root);
}
public TreeNode mirrorTree(TreeNode root) {
if (root == null) {
return null;
}
TreeNode temp = mirrorTree(root.left);
root.left = mirrorTree(root.right);
root.right = temp;
return root;
}
}
import java.util.*;
/**
* 题目描述:
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,
那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
* 解决此问题有多种方法:1. 利用无序数组,实现O(1)插入,O(n)获取 partition函数获取
2. 插入排序实现有序插入O(n),O(1)实现获取
3. AVL树实现O(logn)
4. 两个堆,一个最小堆,一个最大堆,把数据分为两部分,从而实现O(logn)插入,O(1)获取
*/
public class MedianData64 {
//默认是小顶堆 存的是数据流的后半部分
private PriorityQueue minHeap = new PriorityQueue<>();
//自定义排序规则,实现大顶堆 实际上存的是数据流的前半部分
private PriorityQueue maxHeap = new PriorityQueue<>(new Comparator() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
//记录总数 奇数放在大顶堆(前半部分),但要经过小顶堆的判断,,偶数个放在小顶堆 且要经过大顶堆判断
private int count = 0;
public void Insert(Integer num) {
count++;
// 奇数放在最大堆,且确保最大堆放的是数据流的前半部分(即:排序的小的部分)
if ((count & 1) == 1) {
if (!minHeap.isEmpty() && minHeap.peek() < num) {
maxHeap.offer(minHeap.poll());
minHeap.offer(num);
} else {
maxHeap.offer(num);
}
} else {
if (!maxHeap.isEmpty() && maxHeap.peek() > num) {
minHeap.offer(maxHeap.poll());
maxHeap.offer(num);
} else {
minHeap.offer(num);
}
}
}
public Double GetMedian() {
if ((count & 1) == 1) {
return maxHeap.peek() * 1.0;
} else {
return (maxHeap.peek() + minHeap.peek()) / 2.0;
}
}
}
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
/**
* 题目描述:
给定一颗二叉搜索树,请找出其中的第k大的结点。例如, 5 / \ 3 7 /\ /\ 2 4 6 8 中,按结点数值大小顺序第三个结点的值为4。
* 中序遍历到第k个元素即可 1. 用一个容器来保存遍历结果
2. 递归实现,需要用一个全局变量来保存遍历的数目
*/
public class KthNode63 {
private int kthNum = 0;
TreeNode KthNode(TreeNode pRoot, int k) {
if (pRoot == null || k <= 0) {
return null;
}
kthNum = k;
return KthNodeCore(pRoot);
}
//中序遍历,实现肯定就是在中间的逻辑
TreeNode KthNodeCore(TreeNode pRoot) {
// 感觉如果在遍历时写 if(pRoot.left != null){} 那么就可以不写这个边界
// 但是,如果是在循环中遍历,就得写 if(pRoot.left != null) 才能进行下一步遍历
//if (pRoot == null) {
// return null;
//}
TreeNode target = null;
// 左中右
if(pRoot.left!=null) {
target = KthNodeCore(pRoot.left);
}
// 左子树没找到
if (target == null) {
if (kthNum == 1) {
target = pRoot;
}
kthNum--;
}
// 当前节点也不是
if (target == null && pRoot.right != null) {
target = KthNodeCore(pRoot.right);
}
return target;
}
}
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
/**
* 题目描述:请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
* 有两种思想来解决此问题:1. 就是左==右,右==左,就是对称的
* 2. 剑指offer的思想,就是判断前序遍历(跟左右)是否等于对称遍历(根右左),这个思想还要考虑null
*/
public class IsSymmetrical59 {
boolean isSymmetrical(TreeNode pRoot)
{
if (pRoot == null) {
return true;
}
// 递归判断左右子树是否相等
return isSymmetricalLeftAndRight(pRoot.left, pRoot.right);
}
boolean isSymmetricalLeftAndRight(TreeNode left, TreeNode right) {
if (left == null && right == null) {
return true;
}
if (left == null || right == null) {
return false;
}
// 当前需相等,并且其子树也要对称
if (left.val == right.val) {
return isSymmetricalLeftAndRight(left.left, right.right) && isSymmetricalLeftAndRight(left.right, right.left);
}
return false;
}
}
/**
* 题目描述:输入一棵二叉树,判断该二叉树是否是平衡二叉树。
* 只要判断左右子树的高度不超过1,递归实现
* 这样做应该会有大量的子问题存在,子树高度被算了很多次
*/
public class IsBalancedTree {
public boolean IsBalanced_Solution(TreeNode root) {
if(root == null) {
return true;
}
int leftHeight = getHeight(root.left);
int rightHeight = getHeight(root.right);
if (Math.abs(leftHeight - rightHeight) > 1) {
return false;
}
return IsBalanced_Solution(root.left) && IsBalanced_Solution(root.right);
}
/**
* 求树的高度
*/
public int getHeight(TreeNode root) {
if (root == null) {
return 0;
}
return 1 + Math.max(getHeight(root.left), getHeight(root.right));
}
}
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
/**
* 题目描述:输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
* 递归实现,先找相等节点,再每个节点递归判断
*/
public class HasSubtree18 {
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
if (root1 == null || root2 == null) {
return false;
}
// 如果当前节点相等,就进行判断子树是否相等
boolean result = false;
// 先判断当前节点是不是
result = doesTree1HaveTree2(root1, root2) && doesTree1HaveTree2(root1, root2);
// 如果当前节点不是,再判断子树中是否存在
if (!result) {
result = HasSubtree(root1.left, root2);
}
if (!result) {
result = HasSubtree(root1.right, root2);
}
return result;
}
public boolean doesTree1HaveTree2(TreeNode node1, TreeNode node2) {
if (node2 == null) {
return true;
}
// node1结束了,node2还存在
if (node1 == null) {
return false;
}
// 当前节点和子节点都要相等
if (node1.val == node2.val) {
return doesTree1HaveTree2(node1.left, node2.left) && doesTree1HaveTree2(node1.right, node2.right);
}
return false;
}
}
/*
public class TreeLinkNode {
int val;
TreeLinkNode left = null;
TreeLinkNode right = null;
TreeLinkNode next = null;
TreeLinkNode(int val) {
this.val = val;
}
}
*/
/*
题目描述:
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
中序遍历:左根右 pNode是给的节点,返回其下一个节点
1. 如果给定节点有右子树,那么下一个节点肯定是右子树的最左节点
2. 如果没有右子树: 2.1 如果当前节点是其父节点的左子树,那么父节点肯定是下一个数
2.2 如果当前节点还是其父节点的右子树,那么就一直向上找,直到它是其父节点的左子树为止,或者为空(说明它是最右的那一个)
*/
public class GetNextNode58 {
public TreeLinkNode getNext(TreeLinkNode pNode) {
if (pNode == null) {
return null;
}
// 如果有右子树,则返回右子树的最左节点
if (pNode.right != null) {
TreeLinkNode node = pNode.right;
while (node.left != null) {
node = node.left;
}
return node;
} else {
// 没有右子树,且是父节点的左子树,则下一个节点是其父节点
if (pNode.next != null && pNode.next.left == pNode) {
return pNode.next;
}
// 没有右子树,且是父节点的右子树,则下一个节点是直到是父节点的左子树为止
if (pNode.next != null && pNode.next.right == pNode) {
pNode = pNode.next;
while (pNode.next != null && pNode.next.left != pNode) {
pNode = pNode.next;
}
return pNode.next;
}
}
// pNode.next == null 则返回null
return null;
}
}
import java.util.ArrayList;
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
/**
* 题目描述:输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。
路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。
* 递归是一个回溯的过程,其实递归的调用也是一个压栈和出栈的过程
* 采用前序遍历,查找路径,并回溯(其实也是一个DFS的过程)
*/
public class FindPathEqualsK25 {
//声明为全局变量
private ArrayList> listAll = new ArrayList>();
private ArrayList list = new ArrayList();
public ArrayList> FindPath(TreeNode root,int target) {
if(root == null) {
return listAll;
}
list.add(root.val);
target -= root.val;
//到达叶节点且值相等
if(target == 0 && root.left == null && root.right == null)
listAll.add(new ArrayList(list));
//前序遍历 因为是全局变量,所以返回值是没什么卵用的
//从宏观角度来看 中左右 递归就是一个回溯过程
FindPath(root.left, target);
FindPath(root.right, target);
//返回到父节点前,路径要删除当前节点
list.remove(list.size()-1);
return listAll;
}
}
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
/**
*题目描述:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
* 排序的双向链表,肯定是中序遍历实现,则逻辑在中间实现
* 双向链表:用一个tailNode来记录链表的尾节点,则当前节点指向尾,尾再指向当前
*/
public class ConvertSearchTreeToList27 {
TreeNode head = null; // 记录整个双向链表的头节点
TreeNode curTrailHead = null; // 记录当前形成的链表的尾节点 需要全局的
public TreeNode Convert(TreeNode pRootOfTree) {
if (pRootOfTree == null) {
return null;
}
// 左中右
Convert(pRootOfTree.left);
if (head == null) {
head = pRootOfTree;
curTrailHead = pRootOfTree;
} else {
pRootOfTree.left = curTrailHead;
curTrailHead.right = pRootOfTree;
// 更新其尾节点
curTrailHead = pRootOfTree;
}
Convert(pRootOfTree.right);
return head;
}
}