二叉树的问题的解决办法主要采用递归的方法解决
主要有两种:
递归遍历
递归分治
90%的问题可以采用递归分治的办法解决。
1.递归遍历的方法
定义一个全局变量,将该变量作为一个结果参数传入一个定义的空返回值类型的辅助函数。
递归遍历的方法,是一种不断得递归深入遍历,亲力亲为。遍历的顺序很重要。
2.递归分治的方法
分治的办法通常可以不用定义辅助函数,或者定义一个有返回结果的辅助。分治递归左右子树的顺序不重要
分治的方法,是将该任务分配左右子节点。
分治法,关键是怎样利用左右子树的结果,即假设已经获得左右子树的结果,怎样利用左右子树的结果获得全局的结果。
思考一个关系:左右子树的结果和整棵树的结果的关系是什么?
递归的边界条件:首先考虑,root为空的情况
再考虑叶子节点的方法
1.前序遍历的三种写法
private static class TreeNode {
int val;
TreeNode left;
TreeNode right;
public TreeNode(int val) {
this.val = val;
left = null;
right = null;
}
}
public List<Integer> preorderTraversal_1(TreeNode root) {
List<Integer> result = new ArrayList<Integer>();
if (root == null) {
return result;
}
Stack<TreeNode> stack = new Stack<TreeNode>();
stack.push(root);
while(!stack.isEmpty()) {
TreeNode node = stack.pop();
result.add(node.val);
if (node.right != null) {
stack.push(node.right);
}
if (node.left != null) {
stack.push(node.left);
}
}
return result;
}
//用遍历递归的方式
public List<Integer> preorderTraversal_2(TreeNode root) {
List<Integer> result = new ArrayList<Integer>();
preorderCore(root, result);
return result;
}
public void preorderCore(TreeNode root, List<Integer> result) {
if (root == null) {
return ;
}
result.add(root.val);
if (root.left != null) {
preorderCore(root.left, result);
}
if (root.right != null) {
preorderCore(root.right,result);
}
}
//用分而治之递归的方式
public List<Integer> preorderTraversal_3(TreeNode root) {
List<Integer> result = new ArrayList<Integer>();
if (root == null) {
return result;
}
List<Integer> left = preorderTraversal_3(root.left);
List<Integer> right = preorderTraversal_3(root.right);
result.add(root.val);
result.addAll(left);
result.addAll(right);
return result;
}
public class Solution1 {
//分治法
public int maxDepth(TreeNode root) {
if (root == null) {
return 0;
}
int left = maxDepth(root.left);
int right = maxDepth(root.right);
return left > right ? (left + 1) :(right + 1);
}
}
public class Solution_1 {
//递归遍历法
//1.先定义一个全局变量depth用来保存根性最终的树高结果。2.定义一个局部变量保存当前的树高
private int depth;
public int maxDepth1(TreeNode root) {
depth = 0;
helper (root, 1);
return depth;
}
private void helper(TreeNode root, int curDepth) {
if (root == null) {
return ;
}
if (curDepth > depth) {
depth = curDepth;
}
helper(root.left, curDepth + 1);
helper(root.right, curDepth + 1);
}
}
3.求二叉树的所有路径
public class Solution2 {
/*
* 二叉树的所有路径: 用分治的方法
* 递归的拆解:
* 思考这样一个问题:整棵树的结果和左右子树的结果的联系是什么? 即假设已经找到二叉树的左子树的所有路径和右子树的所有路径和根,
* 就是不管三七二十一,先把左节点和右节点传入递归函数中,获得左子树和右子树的所有路径,思考怎样将他和根连接起来?
* 那么现在怎么获得整棵树的所有路径?
* 可以这样解决;
* 将根和左子树的所有路径都连接起来
* 将根和右子树的所有路径都连接起来
*
* 递归的出口:
* 通常递归的出口根是否为空
* 现在要思考当root就是叶子节点时,也要加入到路径中,即当根的左右子树为空时,也要写递归出口
*/
public List<String> binaryTreePaths(TreeNode root) {
List<String> result = new ArrayList<String>();
if (root == null) {
return result;
}
if (root.left == null && root.right == null) {
result.add("" + root.val);
}
List<String> left = binaryTreePaths(root.left);
List<String> right = binaryTreePaths(root.right);
for (String path : left) {
result.add(root.val + "->" + path);
}
for (String path : right) {
result.add(root.val + "->" +path);
}
return result;
}
}
public class Soulution_2 {
/*
* 用递归遍历的方法,定义一个空返回值的辅助函数。
* 递归遍历的辅助函数写法这样去思考:
* 只有根节点的情况下,怎样写它的路径,则将这个初始路径作为参数传入辅助函数,最后递归遍历
* 根据这个初始的路径来递归遍历出所有的路径。
* 那么这种情况下就至少有三个参数传入:1.root,2.只有根节点的路径表示方式,path,3.全局参数,结果result
*/
public List<String> binaryTreePaths(TreeNode root) {
List<String> result = new ArrayList<String>();
if (root == null) {
return result;
}
helper (root, String.valueOf(root.val), result);
return result;
}
private void helper(TreeNode root, String path, List<String> result) {
if (root == null) {
return ;
}
if (root.left == null && root.right ==null) {
result.add(path);
}
if (root.left != null) {//递归遍历调用函数,先左子树再右子树
helper(root.left, path + "->" + String.valueOf(root.left.val), result);
}
if (root.right != null) {
helper(root.right, path + "->" + String.valueOf(root.right.val), result);
}
}
}
4.求出最小和子树
public class Solution3 {
/*
* 求最小和子树,采用分治和遍历递归的结合方式
*/
//首先依据遍历的特点,定义全局的的最小值和全局的节点。
private int curSum = Integer.MAX_VALUE;
private TreeNode sub = null;
public TreeNode findSubtree(TreeNode root) {
helper(root);
return sub;
}
private int helper(TreeNode root) {//分治的方法求出和, 遍历的方式与全局比较
if (root == null) {
return 0;
}
//依据分治的思想,分别求出左右子树的和,再求出当前的和,最后返回的值是当前的子树和,以便下次调用。
int sum = root.val + helper(root.left) + helper(root.right); //用分治的方法
if (sum <= curSum) { //打擂台
curSum = sum;
sub = root;
}
return sum;//返回当前子树的和
}
}
public class Solution {
/*
* 采用完全分治的思想
*/
private class resultType {
public TreeNode sub;
public int minSum;
public int sum;
public resultType(TreeNode sub, int minSum, int sum) { //如何定义构造函数
this.sub = sub;
this.minSum = minSum;
this.sum = sum;
}
}
public TreeNode findSubtree(TreeNode root) {
resultType result = helper(root);
return result.sub;
}
private resultType helper(TreeNode root) {
if (root == null) {
return new resultType(null, Integer.MAX_VALUE, 0);
}
resultType left = helper(root.left);
resultType right = helper(root.right);
resultType result = new resultType(
root,
root.val + left.sum + right.sum,
root.val + left.sum + right.sum
);
if (left.minSum <= result.minSum) {
//result.sub = root.left; //不能直接判断此时的最小节点就是root.left
result.sub = left.sub;
result.minSum = left.minSum;
}
if (right.minSum <= result.minSum) {
//result.sub = root.right;//不能直接就是root.right
result.sub = right.sub;
result.minSum = right.minSum;
}
return result;
}
}
4. 当用分治的方法写辅助函数需要返回两个值的时候,可以重新定义结果类ResultType用来保存返回的结果
/*
* 应用ResultType的典型
* 平衡二叉树的判断,左子树和右子树都是平和二叉树,并且左右子树的高度差不超过1
* 平衡二叉树的高度是一个logN
* 1.需要知道左右子树是否是平衡二叉树
* 2.需要知道整颗二叉树的高度。
* 两个条件需要同时满足才返回它是平衡的,因此写的时候,对两个条件分别判断,当其中之一不满足的时候直接返回false.
* 因此结果需要返回两个值
* 当需要返回多个值的时候就用一个resultType类
*/
public class Solution4 {
private class ResultType {
private boolean isBalanced;
private int high;
public ResultType(boolean isBalanced, int high) {
this.isBalanced = isBalanced;
this.high = high;
}
}
public boolean isBalanced(TreeNode root) {
return helper(root).isBalanced;
}
private ResultType helper(TreeNode root) {
if (root == null) {
return new ResultType(true, 0);
}
ResultType left = helper(root.left);
ResultType right = helper(root.right);
if(!left.isBalanced || !right.isBalanced) {
return new ResultType(false, -1);
}
if(Math.abs(left.high - right.high) > 1) {
return new ResultType(false, -1);
}
return new ResultType(true, Math.max(left.high, right.high)+ 1);
}
}
public class Solution4_1 {
/*
* 不用ResultType时的计算办法
* 主要利用二叉树的高度,只要用二叉树的高度最后不是-1,(用-1来代表非平衡二叉树)那么就是平衡二叉树
* 1.也是用分治的方法求出二叉树的高度,但是要在里面进行判断,如果左右子树的高度差超过1,或者左右子树是非平衡二叉树(-1),那么返回-1.
*/
public boolean isBalanced(TreeNode root) {
return find(root) != -1;
}
public int find(TreeNode root) {
if (root == null) {
return 0;
}
int left = find( root.left );
int right = find( root.right);
if (left == -1 || right == -1 || Math.abs(left - right) > 1) {
return -1;
}
return Math.max(left, right) + 1;
}
}
5. 结合ResType和分治+递归遍历结合的方式
/*
* 最大平均数的子树
* 同样取思考一个问题,整个二叉树的平均值和左子树右子树的平均值由什么关系(没什么关系?尴尬)
* 与求最小和子树一样,用分治+递归遍历的方法,但是此时需要返回两个数,用来计算平均值(一个是子树的和,一个是子树的个数),
* 所以需要用重新定义一个ResulType用来保存子树的和与子树的个数
* 遇到这种二叉树的最大最小子树问题,就要想到打擂台方法,需要定义一个全局的变量,比较当前值取过去值。
* 现在定义这个全局变量有一个特殊的地方,就是要定义一个类型也是ResultType的值来保存过去的最大平均数。一个是TreeNode节点。
*/
public class Solution5 {
private class ResultType {
public int sum, size;
public ResultType(int sum, int size) {
this.sum = sum;
this.size = size;
}
}
private ResultType subtreeResult = null; //定义一个全局的变量用来保存之前的最大平均数(用sum和size)
private TreeNode node = null;
public TreeNode findSubtree2(TreeNode root) {
helper(root);
return node;
}
private ResultType helper(TreeNode root) {
if (root == null) {
return new ResultType(0,0);
}
ResultType left = helper(root.left);
ResultType right = helper(root.right);
ResultType result = new ResultType(
left.sum + right.sum +root.val,
left.size + right.size +1);
if (node == null || result.sum * subtreeResult.size > subtreeResult.sum * result.size) {
subtreeResult = result;
node = root;
}
return result;
}
}