Java中Tree的常见考题
一、树的定义
public static class TreeNode {
String val;
TreeNode left;
TreeNode right;
TreeNode(String x) {
val = x;
}
}
二、树的建立
1.由lists建立树
根据二叉树的性质:
将一个完全二叉树按照从上到下,从左到右进行编号,对树的编号为
1~n
。 其编号为 i
的节点:如果满足2*i<=n
,则说明编号为i的节点有左孩子
,否则没有;
如果满足2*i+1<=n,
则说明编号为i的节点有右孩子
,否则没有;
推出:
- 对于数组编号
1~n
来说:2*i<=n有左孩子;2*i+1<=n有右孩子;
代码:
for(int i=1;i<=a.size()/2;i++) {
if(2*i-1<=a.size()-1)
nodeList.get(i-1).left = nodeList.get(2*i-1);
if(2*i<=a.size()-1)
nodeList.get(i-1).right = nodeList.get(2*i);
}
- 对于数组编号
0~n-1
来说:2*i<=n-1有左孩子;2*i+1<=n-1有右孩子;
代码:
for(int i=0;i<a.size()/2;i++) {
if(2*i<=a.size()-1)
nodeList.get(i).left = nodeList.get(2*i+1);
if(2*i+1<=a.size()-1)
nodeList.get(i).right = nodeList.get(2*i+2);
}
完整代码:
/* 根据List创建二叉树 */
/**
对于数组编号1~n代码:
for(int i=1;i<=a.size()/2;i++) {
if(2*i-1<=a.size()-1)
nodeList.get(i-1).left = nodeList.get(2*i-1);
if(2*i<=a.size()-1)
nodeList.get(i-1).right = nodeList.get(2*i);
}
对于数组编号0~n-1代码:
for(int i=0;i<a.size()/2;i++) {
if(2*i<=a.size()-1)
nodeList.get(i).left = nodeList.get(2*i+1);
if(2*i+1<=a.size()-1)
nodeList.get(i).right = nodeList.get(2*i+2);
}*/
public TreeNode createTree(List<String> lists){
List<TreeNode> nodeList = new ArrayList<>();
if(lists.size()==1)
return new TreeNode(lists.get(0));
for(int i=0;i<lists.size();i++)
nodeList.add(new TreeNode(lists.get(i)));
for(int i=1;i<=lists.size()/2;i++) {
if(2*i-1<=lists.size()-1)
nodeList.get(i-1).left = nodeList.get(2*i-1);
if(2*i<=lists.size()-1)
nodeList.get(i-1).right = nodeList.get(2*i);
}
/*for(int i=0;i<a.size()/2;i++) {
if(2*i<=a.size()-1)
nodeList.get(i).left = nodeList.get(2*i+1);
if(2*i+1<=a.size()-1)
nodeList.get(i).right = nodeList.get(2*i+2);
}*/
return nodeList.get(0);
}
2.指定父节点建立树
思路:检查当前节点的两个位置的子节点是否有空余,有的话添加即可,没有的话就输出报错信息。
/* 指定父节点构造树 */
public void insertNodeToLeft(TreeNode node,String number){
if(node.left == null){
node.left = new TreeNode(number);
}else if(node.right == null){
node.right = new TreeNode(number);
}else{
System.out.println("该节点不可以再插入新结点,因为该节点度数已饱和");
}
}
三、树的遍历
1.先序遍历
使用递归进行遍历,以下三个语句的顺序决定遍历顺序。
res.add(String.valueOf(root.val));
preorderHelper(root.left, res);
preorderHelper(root.right, res);
说明:因为在根据lists生成树的时候将值为null
的也插入了,所以在遍历的时候就不在遍历值为null
的了;
/* 先序遍历 */
public List<String> preorderTraversal(TreeNode root) {
List<String> res = new ArrayList<>();
preorderHelper(root, res);
return res;
}
private void preorderHelper(TreeNode root, List<String> res) {
if (root == null) return;
if(root.val != "null"){
res.add(String.valueOf(root.val));
}
preorderHelper(root.left, res);
preorderHelper(root.right, res);
}
2.中序遍历
/* 中序遍历 */
public List<String> inorderTraversal(TreeNode root) {
List<String> res = new ArrayList<>();
inorderHelper(root, res);
return res;
}
private void inorderHelper(TreeNode root, List<String> res) {
if (root == null) return;
inorderHelper(root.left, res);
if(root.val != "null"){
res.add(String.valueOf(root.val));
}
inorderHelper(root.right, res);
}
3.后序遍历
/* 后序遍历 */
public List<String> postorderTraversal(TreeNode root) {
List<String> res = new ArrayList<>();
postorderHelper(root, res);
return res;
}
private void postorderHelper(TreeNode root, List<String> res) {
if (root == null) return;
postorderHelper(root.left, res);
postorderHelper(root.right, res);
if(root.val != "null"){
res.add(String.valueOf(root.val));
}
}
4.层次遍历
最简单的解法就是递归,首先确认树非空,然后调用递归函数 helper(node, level),参数是当前节点和节点的层次。程序过程如下:
输出列表称为 levels,当前最高层数就是列表的长度 len(levels)。比较访问节点所在的层次 level 和当前最高层次 len(levels) 的大小,如果前者更大就向 levels 添加一个空列表。
将当前节点插入到对应层的列表 levels[level] 中。
递归非空的孩子节点:helper(node.left / node.right, level + 1)
参考:https://leetcode-cn.com/problems/binary-tree-level-order-traversal/solution/er-cha-shu-de-ceng-ci-bian-li-by-leetcode/
/* 层次遍历 */
List<List<String>> levels = new ArrayList<>();
public void helper(TreeNode node, int level) {
// start the current level
if (levels.size() == level)
levels.add(new ArrayList<>());
// fulfil the current level
if(!node.val.equals("null")){
levels.get(level).add(node.val);
}
// process child nodes for the next level
if (node.left != null)
helper(node.left, level + 1);
if (node.right != null)
helper(node.right, level + 1);
}
public List<List<String>> levelOrder(TreeNode root) {
if (root == null) return levels;
helper(root, 0);
return levels;
}
四、二叉树的最大深度和最小深度
递归:https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/solution/er-cha-shu-de-zui-da-shen-du-by-leetcode/
1. 最大深度
/* 二叉树最大深度 */
int maxDeath(TreeNode node){
if(node==null){
return 0;
}
int left = maxDeath(node.left);
int right = maxDeath(node.right);
return Math.max(left,right) + 1;
}
2.最小深度
/* 二叉树最小深度 */
int getMinDepth(TreeNode root){
if(root == null){
return 0;
}
return getMin(root);
}
int getMin(TreeNode root){
if(root == null){
return Integer.MAX_VALUE;
}
if(root.left == null&&root.right == null){
return 1;
}
return Math.min(getMin(root.left),getMin(root.right)) + 1;
}
五、节点个数
1.节结点个数
/* 二叉树节点个数 */
int numOfTreeNode(TreeNode root){
if(root == null){
return 0;
}
int left = numOfTreeNode(root.left);
int right = numOfTreeNode(root.right);
return left + right + 1;
}
2.第k层结点个数
/* 二叉树第k层节点个数 */
int numsOfkLevelTreeNode(TreeNode root,int k){
if(root == null||k<1){
return 0;
}
if(k==1){
return 1;
}
int numsLeft = numsOfkLevelTreeNode(root.left,k-1);
int numsRight = numsOfkLevelTreeNode(root.right,k-1);
return numsLeft + numsRight;
}
1.叶子结点个数
/* 二叉树叶子结点个数 */
int numsOfNoChildNode(TreeNode root){
if(root == null){
return 0;
}
if(root.left==null&&root.right==null){
return 1;
}
return numsOfNoChildNode(root.left)+numsOfNoChildNode(root.right);
}
六、判断二叉树是否为平衡二叉树
/* 判断二叉树是否为平衡二叉树 */
boolean isBalanced(TreeNode node){
return maxDeath2(node)!=-1;
}
int maxDeath2(TreeNode node){
if(node == null){
return 0;
}
int left = maxDeath2(node.left);
int right = maxDeath2(node.right);
if(left==-1||right==-1||Math.abs(left-right)>1){
return -1;
}
return Math.max(left, right) + 1;
}
七、二叉树是否相同与二叉树是否互为镜像
/*二叉树是否相同*/
boolean isSameTreeNode(TreeNode t1,TreeNode t2){
if(t1==null&&t2==null){
return true;
}
else if(t1==null||t2==null){
return false;
}
if(t1.val != t2.val){
return false;
}
boolean left = isSameTreeNode(t1.left,t2.left);
boolean right = isSameTreeNode(t1.right,t2.right);
return left&&right;
}
/* 二叉树是否互为镜像*/
boolean isMirror(TreeNode t1,TreeNode t2){
if(t1==null&&t2==null){
return true;
}
if(t1==null||t2==null){
return false;
}
if(t1.val != t2.val){
return false;
}
return isMirror(t1.left,t2.right)&&isMirror(t1.right,t2.left);
}
八、两个二叉树最小公共祖先
解决方法:递归
首先在二叉树中搜索给定的节点 p 和 q,然后找到它们的最近共同祖先。我们可以使用普通的树遍历来搜索这两个节点。一旦我们达到所需的节点 p和 q,我们就可以回溯并找到最近的共同祖先。
这种方法非常直观。先深度遍历该树。当你遇到节点 p 或 q 时,返回一些布尔标记。该标志有助于确定是否在任何路径中找到了所需的节点。最不常见的祖先将是两个子树递归都返回真标志的节点。它也可以是一个节点,它本身是p或q中的一个,对于这个节点,子树递归返回一个真标志。
让我们看看基于这个想法的形式算法。
算法:
从根节点开始遍历树。
如果当前节点本身是 p 或 q 中的一个,我们会将变量 mid 标记为 true,并继续搜索左右分支中的另一个节点。
如果左分支或右分支中的任何一个返回 true,则表示在下面找到了两个节点中的一个。
如果在遍历的任何点上,左、右或中三个标志中的任意两个变为 true,这意味着我们找到了节点 p 和 q 的最近公共祖先。
/* 两个二叉树最小公共祖先*/
private TreeNode ans = null;
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
recurseTree(root, p, q);
return ans;
}
private boolean recurseTree(TreeNode currentNode, TreeNode p, TreeNode q) {
// If reached the end of a branch, return false.
if (currentNode == null) {
return false;
}
// Left Recursion. If left recursion returns true, set left = 1 else 0
int left = recurseTree(currentNode.left, p, q) ? 1 : 0;
// Right Recursion
int right = recurseTree(currentNode.right, p, q) ? 1 : 0;
// If the current node is one of p or q
int mid = (currentNode == p || currentNode == q) ? 1 : 0;
// If any two of the flags left, right or mid become True
if (mid + left + right >= 2) {
ans = currentNode;
}
// Return true if any one of the three bool values is True.
return (mid + left + right > 0);
}
九、查找节点node是否在当前二叉树中
// 查找节点node是否在当前二叉树中
boolean findNode(TreeNode root,TreeNode node){
if(root == null || node == null){
return false;
}
if(root == node){
return true;
}
boolean found = findNode(root.left,node);
if(!found){
found = findNode(root.right,node);
}
return found;
}
十、二叉树内两个节点的最长距离
二叉树中两个节点的最长距离可能有两种情况:
- 左子树的最大深度+右子树的最大深度为二叉树的最长距离
- 左子树或右子树中的最长距离即为二叉树的最长距离
因此,递归求解即可
private static class Result{
int maxDistance;
int maxDepth;
public Result() {
}
public Result(int maxDistance, int maxDepth) {
this.maxDistance = maxDistance;
this.maxDepth = maxDepth;
}
}
int getMaxDistance(TreeNode root){
return getMaxDistanceResult(root).maxDistance;
}
Result getMaxDistanceResult(TreeNode root){
if(root == null){
Result empty = new Result(0,-1);
return empty;
}
Result lmd = getMaxDistanceResult(root.left);
Result rmd = getMaxDistanceResult(root.right);
Result result = new Result();
result.maxDepth = Math.max(lmd.maxDepth,rmd.maxDepth) + 1;
result.maxDistance = Math.max(lmd.maxDepth + rmd.maxDepth,Math.max(lmd.maxDistance,rmd.maxDistance));
return result;
}
十一、将有序数组转为二叉搜索树
题目给出的升序数组就是二叉搜索树的中序遍历。
根据中序遍历还原一颗树,根据根节点,就可以递归的生成左右子树。
这里的话怎么知道根节点呢?平衡二叉树,既然要做到平衡,我们只要把根节点选为数组的中点即可。
综上,和之前一样,找到了根节点,然后把数组一分为二,进入递归即可。注意这里的边界情况,包括左边界,不包括右边界。
/* 将有序数组转为二叉搜索树*/
public TreeNode sortedArrayToBST(List<String> nums) {
return sortedArrayToBST(nums, 0, nums.size());
}
private TreeNode sortedArrayToBST(List<String> nums, int start, int end) {
if (start == end) {
return null;
}
int mid = (start + end)/2;
TreeNode root = new TreeNode(nums.get(mid));
root.left = sortedArrayToBST(nums, start, mid);
root.right = sortedArrayToBST(nums, mid + 1, end);
return root;
}
所有源码:https://github.com/zhangzhishun/java