【数据结构与算法】二叉树整合[leetcode]

递归

平衡二叉树

LeetCode:平衡二叉

题目描述:

给定一个二叉树,判断它是否是高度平衡的二叉树。

示例:

给定二叉树 [3,9,20,null,null,15,7]
    3
   / \
  9  20
    /  \
   15   7
返回true

思想:

使用实例域变量记录是否与有不满足平衡的节点出现,使用一次递归即可。

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    private boolean flag = true;
    //返回高度,并且在递归过程中记录flag
    private int treeDepth(TreeNode root){
        if(root ==null)
            return 0;
        int leftLen = treeDepth(root.left);
        int rightLen = treeDepth(root.right);
        if(Math.abs(leftLen-rightLen)>1)
            flag = false;
        return Math.max(leftLen,rightLen)+1;
    }
    public boolean isBalanced(TreeNode root) {
        int i = treeDepth(root);
        return flag;
    }
}

二叉树的直径

LeetCode:二叉树的直径

题目描述:

给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。

示例:

给定二叉树

          1
         / \
        2   3
       / \     
      4   5    
返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。

思想:

每一个结点的左子树深度+右子树深度之和,最大的一个,就是结果。完全可以在递归中做到。

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    private int maxPath=0;
    public int diameterOfBinaryTree(TreeNode root) {
        treeDepth(root);
        return maxPath;
    }
    private int treeDepth(TreeNode root){
        if(root==null)
            return 0;
        int x=treeDepth(root.left);
        int y=treeDepth(root.right);
        maxPath=Math.max(x+y,maxPath);
        return Math.max(x,y)+1;
    }
}

路径总和

LeetCode:路径总和 III

题目描述:

给定一个二叉树,它的每个结点都存放着一个整数值。

找出路径和等于给定数值的路径总数。

路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。

二叉树不超过1000个节点,且节点数值范围是 [-1000000,1000000] 的整数。

示例:

root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8

      10
     /  \
    5   -3
   / \    \
  3   2   11
 / \   \
3  -2   1

返回 3。和等于 8 的路径有:

1.  5 -> 3
2.  5 -> 2 -> 1
3.  -3 -> 11

思想:

使用双层递归,第一层递归遍历每个结点,从每个结点开始递归寻找以该结点为起点的路径。

注意:找到合适路径末端时,不要着急return,继续向下,因为有些路径是重叠的(起点一致,终点不一样)

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public int pathSum(TreeNode root, int sum) {
        if(root == null) return 0;
        return pathSumStartWithRoot(root,sum)+pathSum(root.left,sum)+pathSum(root.right,sum);
    }

    private int pathSumStartWithRoot(TreeNode root, int sum){
        if(root == null) return 0;
        int ret = (sum == root.val)?1:0;
        return ret+pathSumStartWithRoot(root.left,sum-root.val)+pathSumStartWithRoot(root.right,sum-root.val);
    }
}

另一棵树的子树

LeetCode:另一棵树的子树

题目描述:

给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。

示例:

给定的树 s:

     3
    / \
   4   5
  / \
 1   2
给定的树 t:

   4 
  / \
 1   2
 返回 true,因为 t 与 s 的一个子树拥有相同的结构和节点值。

思想:

双递归,注意下面的代码

if(!isEqual(s,t)){
    return isSubtree(s.left,t)||isSubtree(s.right,t);
}
return true;

可以简化为:

return isEqual(s,t)||isSubtree(s.left,t)||isSubtree(s.right,t);

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    private boolean isEqual(TreeNode s, TreeNode t){
        if(s==null&&t==null)
            return true;
        if(s==null||t==null||s.val!=t.val)
            return false;
        return isEqual(s.left,t.left)&&isEqual(s.right,t.right);
    }
    public boolean isSubtree(TreeNode s, TreeNode t) {
        if(s==null&&t!=null)
            return false; 
        return isEqual(s,t)||isSubtree(s.left,t)||isSubtree(s.right,t);
    }
}

对称二叉树

LeetCode:对称二叉树

题目描述:

给定一个二叉树,检查它是否是镜像对称的。

示例:

例如,二叉树 [1,2,2,3,4,4,3] 是对称的。

    1
   / \
  2   2
 / \ / \
3  4 4  3
返回true

思想:

1.怎么判断一棵树是不是对称二叉树? 答案:如果所给根节点,为空,那么是对称。如果不为空的话,当他的左子树与右子树对称时,他对称

2.那么怎么知道左子树与右子树对不对称呢?在这我直接叫为左树和右树 答案:如果左树的左孩子与右树的右孩子对称,左树的右孩子与右树的左孩子对称,那么这个左树和右树就对称。

仔细一想就明白了,递归写起来很简单。

代码

class Solution {
    public boolean isSymmetric(TreeNode root) {
        if(root==null) return true;
        return isSonSymmetric(root.left, root.right);
    }
    private boolean isSonSymmetric(TreeNode m, TreeNode n){
        if(m==null&&n==null)
            return true;
        if(m==null||n==null||m.val!=n.val)
            return false;
        return isSonSymmetric(m.left,n.right)&&isSonSymmetric(m.right,n.left);
    }
}

二叉树的最小深度

LeetCode:二叉树的最小深度

题目描述:

给定一个二叉树,找出其最小深度。

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

说明: 叶子节点是指没有子节点的节点

示例:

给定二叉树 [3,9,20,null,null,15,7],

    3
   / \
  9  20
    /  \
   15   7
返回它的最小深度  2.

思想:

还是递归,没啥特别的

代码

我的第一遍代码:

public int minDepth(TreeNode root) {
    if(root==null)
        return 0;
    if(root.left==null&&root.right==null)
        return 1;
    if(root.left==null)
        return 1+minDepth(root.right);
    if(root.right==null)
        return 1+minDepth(root.left);
    return 1+Math.min(minDepth(root.left),minDepth(root.right));
}

优化之后:

class Solution {
    public int minDepth(TreeNode root) {
        if(root==null) return 0;
        int x=minDepth(root.right);
        int y=minDepth(root.left);
        if(x==0||y==0)
            return x+y+1;
        return Math.min(x,y)+1;
    }
}

if(x == 0||y==0) return x+y+1; 非常妙

最长同值路径

LeetCode:最长同值路径

题目描述:

给定一个二叉树,找到最长的路径,这个路径中的每个节点具有相同值。 这条路径可以经过也可以不经过根节点。

注意:两个节点之间的路径长度由它们之间的边数表示。

示例:

输入:

              1
             / \
            4   5
           / \   \
          4   4   5
输出:

2

思想:

可以设置一个全局变量res,只需要一次递归完成。递归过程中更新这个全局变量。

每递归遍历一个结点,可以得到从左开始的最大长度leftLen,和从右开始的最大长度rightLen,两者相加用于更新res,取max用于返回到上一层递归。

代码

  • 笨办法
class Solution {
    private int maxLen(TreeNode root,int val){
        if(root==null||root.val != val)
            return 0;
        return 1+Math.max(maxLen(root.left,val),maxLen(root.right,val));
    }
    public int longestUnivaluePath(TreeNode root) {
        if(root == null)
            return 0;
        int L = maxLen(root.left,root.val)+maxLen(root.right,root.val);
        return Math.max(L,Math.max(longestUnivaluePath(root.left),longestUnivaluePath(root.right)));
    }
}
  • 巧妙方法
class Solution {
    private int res = 0;
    private int dfs(TreeNode root){
        if(root==null)
            return 0;
        //以下一个结点为起点的最长路径值
        int left = dfs(root.left);
        int right = dfs(root.right);
        //若当前结点root值与下一个结点相等,则加1
        int leftLen = root.left!=null&&root.val==root.left.val?(left+1):0;
        int rightLen = root.right!=null&&root.val==root.right.val?(right+1):0;
        //leftLen + rightLen两个加起来,就是经过root的最长路径值
        res = (leftLen + rightLen)>res?(leftLen + rightLen):res;
        return Math.max(leftLen,rightLen);//以root为起点的最长路径值
    }
    public int longestUnivaluePath(TreeNode root) {
        dfs(root);
        return res;
    }
}

间隔遍历-打家劫舍

LeetCode:打家劫舍 III

题目描述:

在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。

计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。

示例:

输入: [3,2,3,null,3,null,1]

     3
    / \
   2   3
    \   \ 
     3   1

输出: 7 
解释: 小偷一晚能够盗取的最高金额 = 3 + 3 + 1 = 7.

思想:

     A
    / \
   B   C
  / \   \ 
 D   E   F

间隔遍历,每次递归时,有两种情况,A加上DEF开始的结果,或者B和C为根的结果之和。第二种情况可能B和C都不去偷,但这个结果一定被第一种情况囊括了,换句话说,一定是小于第一种情况的值。(貌似说得有点含糊)

代码

笨方法:

class Solution {
    private int max(TreeNode root , boolean steal){
        if(root==null) return 0;
        if(steal){
            return root.val + max(root.left,false)+max(root.right,false);
        }else{
            return Math.max(max(root.left,false),max(root.left,true))+Math.max(max(root.right,false),max(root.right,true));
        }
    }
    public int rob(TreeNode root) {
        return Math.max(max(root,false),max(root,true));
    }
}

正确方法:

class Solution {
    public int rob(TreeNode root) {
        if(root==null) return 0;
        int val1 = root.val;
        if(root.left!=null) val1+=rob(root.left.right)+rob(root.left.left);
        if(root.right!=null) val1+=rob(root.right.left)+rob(root.right.right);
        int val2 = rob(root.left)+rob(root.right);
        return Math.max(val1,val2);
    }
}

方法二比方法一好的地方在于:方法二每个结点最多只遍历了两遍,而方法一中,每次遍历都要以两种状态考虑一个结点,偷或者不偷,所以第一个结点访问2次,第二层每个结点访问1+2次(上一个结点选择偷,分裂出1次,选择不偷,分裂出2次),第3层的结点要访问3+2呢,第n层访问多少次呢,不算了,头要裂开,反正一定比方法一多了去。

二叉树中第二小的结点

LeetCode:二叉树中第二小的结点

题目描述:

给定一个非空特殊的二叉树,每个节点都是正数,并且每个节点的子节点数量只能为 2 或 0。如果一个节点有两个子节点的话,那么这个节点的值不大于它的子节点的值。

给出这样的一个二叉树,你需要输出所有节点中的第二小的值。如果第二小的值不存在的话,输出 -1 。

示例:

输入: 
    2
   / \
  2   5
     / \
    5   7

输出: 5
说明: 最小的值是 2 ,第二小的值是 5 。

思想:

注意审题,子节点数量只可能是0或者2

递归找不到比根更大的值时,就继续递归,一直找不到就返回-1;若找到比根更大的数就记录下来,停止递归,对左右两个结果做个比较。

代码

class Solution {
    public int findSecondMinimumValue(TreeNode root) {
        if(root==null) return -1;
        int leftMin,rightMin;
        if(root.left==null&&root.right==null)
            return -1;
        leftMin = root.left.val == root.val?findSecondMinimumValue(root.left):root.left.val;
        rightMin = root.right.val == root.val?findSecondMinimumValue(root.right):root.right.val;
        return leftMin>0&&rightMin>0?Math.min(leftMin,rightMin):Math.max(leftMin,rightMin);
    }
}

队列与栈的辅助

二叉树的层平均值

LeetCode:二叉树的层平均值

题目描述:

给定一个非空二叉树, 返回一个由每层节点平均值组成的数组.

示例:

输入:
    3
   / \
  9  20
    /  \
   15   7
输出: [3, 14.5, 11]
解释:
第0层的平均值是 3,  第1层是 14.5, 第2层是 11. 因此返回 [3, 14.5, 11].

思想:

使用一个队列进行层次遍历,BFS。具体思路如下:

  1. 首先外层一定有一个循环,循环开始时,队列存储这一层所有节点,循环体末尾,队列存储好下一层所有节点;
  2. 循环内部需要做的:再用一层循环,执行出队,累加求和取平均值,同时将出队节点的左右孩子节点再次加入队列;
  3. 如果内层循环条件是isEmpty(),循环无法在上一层节点遍历完毕时结束。所以应该用size()记录队列长度,作为循环结束的条件。

代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public List<Double> averageOfLevels(TreeNode root) {
        List<Double> res = new ArrayList<>();
        if(root==null) return res; 
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        while(!queue.isEmpty()){
            double n = queue.size(),m=0;
            for(int i=0;i<n;++i){
                TreeNode temp = queue.poll();
                m+=temp.val;
                if(temp.left!=null) queue.add(temp.left);
                if(temp.right!=null) queue.add(temp.right);
            }
            res.add(m/n);
        }
        return res;
    }
}

找树左下角的值

LeetCode:找树左下角的值

题目描述:

给定一个二叉树,在树的最后一行找到最左边的值。

示例:

输入:

    2
   / \
  1   3

输出:
1

思想:

依然使用一个队列进行BFS,注意是从右往左。

代码

class Solution {
    public int findBottomLeftValue(TreeNode root) {
        TreeNode temp=null;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        while(!queue.isEmpty()){
            temp = queue.poll();
            if(temp.right!=null) queue.add(temp.right);
            if(temp.left!=null) queue.add(temp.left);
        }
        return temp.val;
    }
}

二叉树的前中后序遍历

LeetCode:二叉树的前序遍历

LeetCode:二叉树的中序遍历

LeetCode:二叉树的后序遍历

题目描述:

前中后序遍历

思想:

迭代思想,都需要使用栈来辅助。

  • 前序遍历:根左右;循环体的思想很直白,先pop出一个结点,计入val,再push进右孩子结点和左孩子结点,进入下一次循环。
  • 中序遍历:左根右;首先想到,每一次循环中,有一个TreeNode指针可以指代一个结点,和一个stack能pop出一个结点,这两个结点可以不一样的,都利用起来;让指针指向刚入栈的左孩子,或者刚出栈的右孩子;可以想到,判断条件设置为指针指代的结点是否为空,如果不为空就将该结点入栈,再指向左孩子,如果为空,则出栈一个结点,计入出栈结点的val,再让指针指向出栈结点的右孩子;注意while条件中要加一个root!=null。
  • 后序遍历:左右根;这个也很巧妙,按照前序遍历的方法,修改为根右左,每次计val的时候逆序插入(前插),最后就能得到后序遍历结果。

代码

  • 前序遍历
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        if(root==null) return res;
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while(!stack.isEmpty()){
            root = stack.pop();
            if(root==null) continue;
            res.add(root.val);
            stack.push(root.right);
            stack.push(root.left);
        }
        return res;
    }
}
  • 中序遍历
class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        Stack<TreeNode> stack = new Stack<>();
        List<Integer> res = new ArrayList<>();
        while(!stack.isEmpty()||root!=null){
            if(root==null){
                root = stack.pop();
                res.add(root.val);
                root = root.right;
            }else{
                stack.push(root);
                root=root.left;
            }
        }
        return res;
    }
}
  • 后序遍历
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<>();
        if(root==null) return res;
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while(!stack.isEmpty()){
            root = stack.pop();
            if(root.left!=null) stack.push(root.left);
            if(root.right!=null) stack.push(root.right);
            res.add(0,root.val);//逆序添加结点值
        }
        return res;
    }
}
posted @ 2020-04-17 10:56  数小钱钱的种花兔  阅读(217)  评论(0编辑  收藏  举报