二叉树的问题基本上都是深度优先搜索
二叉树上求值,求路径
max/min/averge/sum/path
二叉树结构变化
二叉查找树 BST
遍历 traversal 递归没有返回值,需要一个全局变量
分治 divide&conquer 递归有返回值
分治法--遇到二叉树问题,想想整棵树在问题上的结果和左右子树在该问题上的结果之间的联系是什么
一 二叉树上求值,求路径
例 lintcode 596. Minimum Subtree https://www.lintcode.com/problem/minimum-subtree/description
traversal + divide conquer 。 traversal体现在用一个全局变量去打擂台,分治的思想体现在每次都递归求左右子树的最小和,而不是遍历。既然需要递归地求左右子树的最小值,那么递归函数就需要一个返回值。如果单纯的用divide conquer,返回值的类型应该是问题答案的类型,因为没有全局变量。在这里可以是一个在过程中需要的值的类型。
每一次求左右子树的和,然后和全局变量打擂台即可。
public class Solution {
/**
* @param root: the root of binary tree
* @return: the root of the minimum subtree
*/
/*divdie conquer(recursion on left and right) + traversal(global variable )*/
private int minSum = Integer.MAX_VALUE;
private TreeNode res = null;
public TreeNode findSubtree(TreeNode root) {
helper(root);
return res;
}
public int helper(TreeNode root){
if(root == null){
return 0;
}
int left = helper(root.left);
int right = helper(root.right);
int sum = left + right + root.val;
if(sum < minSum){
minSum = sum;
res = root;
}
return sum;
}
第二种 divide conquer
答案类型是TreeNode, 而在解题中需要比较int的大小,所以需要建一个class将需要的变量装进去。
分治的思想在于,得到左右子树的分别的最小和,以及root.val + 左右子树的全部和, 三个值作比较,对应返回左子的最小和的头,右子的最小和的头,或者root。class 中需要有 子树最小和,子树最小和的头,自己的全部和三个变量。
class Subtree{
int minSum;
TreeNode minRoot;
int rootSum;
public Subtree(int minSum, TreeNode minRoot, int rootSum){
this.minSum = minSum;
this.minRoot = minRoot;
this.rootSum = rootSum;
}
}
public TreeNode findSubtree(TreeNode root) {
Subtree res = helper(root);
return res.minRoot;
}
public Subtree helper(TreeNode root){
if(root == null){
return new Subtree(Integer.MAX_VALUE, null, 0);
}
Subtree left = helper(root.left);
Subtree right = helper(root.right);
Subtree res = new Subtree(
left.rootSum + right.rootSum + root.val,
root,
left.rootSum + right.rootSum + root.val
);
if(res.minSum > left.minSum){
res.minSum = left.minSum;
res.minRoot = left.minRoot;
}
if(res.minSum > right.minSum){
res.minSum = right.minSum;
res.minRoot = right.minRoot;
}
return res;
}
例 lintcode 480. Binary Tree Paths https://www.lintcode.com/problem/binary-tree-paths/description
找到所有的 根到叶子的路径
traversal 的思路:从根开始,向左右recursion,每往下走一个节点就在记录(String)上加上当前的root的值。当走到null 的时候,把这条路径加入到结果集中去。recursion function的参数包括了最终返回的结果集List
public List binaryTreePaths(TreeNode root) {
List res = new ArrayList<>();
if(root == null)return res;
helper(root, String.valueOf(root.val), res);
return res;
}
public void helper(TreeNode root, String path, List res){
if(root.left == null && root.right == null){
res.add(path);
return;
}
if(root.left != null){
helper(root.left, path +"->"+ root.left.val,res);
}
if(root.right != null){
helper(root.right, path +"->"+ root.right.val,res);
}
}
divde conquer:分治的思路是对于左右子树的所有路径,都在前面加上root自己的val。返回值是题目答案的类型,左右子树recursion的返回值是结果集。为null时返回空结果集。叶子节点应该返回只包含自己的结果集。 因为返回空结果集时没有办法进行遍历加上自己,所以需要多一次判断,判断是否为叶子节点。
public List binaryTreePaths(TreeNode root) {
List res = new ArrayList<>();
if(root == null)return res;
return helper2(root);
}
public List helper2(TreeNode root){
List res = new LinkedList<>();
if(root == null){
return res;
}
List leftRes = helper2(root.left);
List rightRes = helper2(root.right);
for(String s : leftRes){
//s = root.val + "->" + s;
res.add(root.val + "->" + s);
}
for(String s : rightRes){
//s = root.val + "->" + s;
res.add(root.val + "->" + s);
}
if(res.size() == 0){
res.add(root.val + "");
}
return res;
}
follow up 把String 改成 StringBuilder
时间复杂度 O(nlogn)为最大值。满二叉树,traversal方法,即对n/2个叶子节点,n/2条路径,每条路径都要用O(logn)的时间加值,divide conquer方法,每个点都要遍历一次它自己的所有路径,相当于一共n/2条的路径,在每一层都要被遍历一遍。
找路径时 O(路径数*构造每条路径的时间)
找点 O(n*每个点的处理时间)
例 lintcode 88. Lowest Common Ancestor of a Binary Tree https://www.lintcode.com/problem/lowest-common-ancestor-of-a-binary-tree/description
traversal:用先序遍历的顺序,每走一个节点就记录值和对应层级,在值的记录中找到一对AB,两者之间层级最高的就是lowest common ancestor。
divide conquer:
首先分情况考虑,假如直白的想,recursion返回的是LCA或者是null,AB都在左子树,左子树返回LCA,右子树返回null;AB都在右子树,右子树返回LCA,左子树返回null;AB分别在左子树和右子树,都没有LCA, 都返回null,那LCA就是root;最后一种情况,左右子树都没有AB,如果都返回null就和上面一种情况冲突了。
所以换一种思路,当recursion到A或者B的时候,返回A或者B,recursion的终止当然还是遇到null返回null。当左右都是null时,返回null;左右子树只有一个不是null,就返回那个不是的;如果都不是null,那就是找到了LCA, 返回root。
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode A, TreeNode B) {
if(root == A || root == B) return root;
return helper(root, A, B);
}
public TreeNode helper(TreeNode root, TreeNode A, TreeNode B){
if(root == null)return null;
if(root == A || root == B)return root;
TreeNode left = helper(root.left, A, B);
TreeNode right = helper(root.right, A, B);
if(left == null && right == null){
return null;
}
if(left == null && right != null){
return right;
}
if(right == null && left != null){
return left;
}
return root;
}
}
例 lintcode 578. Lowest Common Ancestor https://www.lintcode.com/problem/lowest-common-ancestor-iii/description?_from=ladder&&fromId=1
88的题解可以成立的前提条件是一定有AB,如果只有一个的话那最终就会把那一个返回。
因为在解题中还需要知道是否有AB,在recursion中要传递这个信息,所以建一个class,包括进去LCA与AB是否存在的信息
class IsContain{
TreeNode LCA;
boolean isA;
boolean isB;
public IsContain(TreeNode LCA, boolean isA, boolean isB){
this.LCA = LCA;
this.isA = isA;
this.isB = isB;
}
}
public TreeNode lowestCommonAncestor3(TreeNode root, TreeNode A, TreeNode B) {
IsContain res = helper(root, A, B);
if(!res.isA || !res.isB){
return null;
}else{
return res.LCA;
}
}
public IsContain helper(TreeNode root, TreeNode A, TreeNode B){
if(root == null){
return new IsContain(null, false,false);
}
IsContain left = helper(root.left, A, B);
IsContain right = helper(root.right, A, B);
boolean isANow = left.isA||right.isA||root == A;
boolean isBNow = left.isB||right.isB||root == B;
if(root == A){
return new IsContain(A, isANow, isBNow);
}
if(root == B){
return new IsContain(B, isANow, isBNow);
}
if(left.LCA == null && right.LCA == null){
return new IsContain(null,isANow,isBNow);
}
if(left.LCA != null && right.LCA != null){
return new IsContain(root, true, true);
}
if(left.LCA != null){
return new IsContain(left.LCA,isANow,isBNow);
}
return new IsContain(right.LCA, isANow, isBNow);
}
}