543. 二叉树的直径

乍看是 根节点的 左子树最大高度 + 右子树最大高度 + 1 (如图一)

但其实不能这样,因为路径可能并不经过根节点(如图二)

因此要用一个 max 来保存最后的最大路径和

在求二叉树高度的递归中,在每个根节点(在每次递归中),比较 max 与 以这个当前根节点的  左子树最大高度 + 右子树最大高度 + 1 

图一

图二

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {

    private static int maxDiameter = 0;

    public Solution() {
        maxDiameter = 0;
    }

    public int diameterOfBinaryTree(TreeNode root) {
        dfs(root);
        // 在递归求树高的过程中求出来的 maxDiameter
        return maxDiameter;
    }

    private int dfs(TreeNode nowRoot) {
        if (nowRoot == null) {
            return 0;
        }
        int leftHeight = dfs(nowRoot.left);
        int rightHeight = dfs(nowRoot.right);

        // 路径长度最大值为左子树 + 右子树高度,与之前的最大值比较
        maxDiameter = Math.max(leftHeight + rightHeight, maxDiameter);

        // 因为要通过递归求树高度,所以最后仍要返回高度:左右子树中较高的那个(而不是路径和)
        return Math.max(leftHeight, rightHeight) + 1;
    }
}

 

124. 二叉树中的最大路径和

最大路径怎么求?

最后的最大路径,任一节点 都有可能作为这个最大路径的根节点

所以有一个全局的最大路径 maxPathSum, 每次递归的时候(在每个根节点)都要与之比较

如果当前节点作为 最后最大路径的根节点,那么 只当前根节点/当前根节点+左子树/当前根节点+右子树/当前根节点+左子树+右子树 这四种情况都行

所以,要取这四种情况中最大的来与 maxPathSum 比较这四种一定会包含当前根节点,所以四种中最大的求法就是:初始值为当前根节点的值,左子树>0 就加左子树,右子树>0 就加右子树

递归的返回值应该是什么?

之前比较路径的时候,由于当前节点可能作为最后最长路径的根,所以要把四种情况都算上

但是返回的时候,前节点的情况是作为之后的一些结果的子集,这时候当前节点一定不能为最长路径的根节点

所以要么选择左分支 root.val + leftSum,要么选择右分支 root.val + rightSum, 取两者中较大的那个,如果收益小于0,直接舍弃返回 0

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {

    private static int maxPathSum = Integer.MIN_VALUE;

    public Solution() {
        maxPathSum = Integer.MIN_VALUE;
    }

    public int maxPathSum(TreeNode root) {
        dfs(root);
        return maxPathSum;
    }

    private int dfs(TreeNode root) {
        int thisRootSum = 0;
        if (root == null) {
            return thisRootSum;
        }

        int leftSum = dfs(root.left);
        int rightSum = dfs(root.right);
        
        // 最长路径可能以任一结点为根。只当前根节点/根节点+左子树/根节点+右子树/根节点+左子树+右子树 这四种情况都行,选出这四种情况下最大的
        int thisRootMax = root.val;
        if (leftSum > 0) {
            thisRootMax += leftSum;
        }
        if (rightSum > 0) {
            thisRootMax += rightSum;
        }
        // 最长路径可能以任一结点为根,所以每个节点的最大与之前的最大比较一下
        maxPathSum = Math.max(maxPathSum, thisRootMax);
        
        // 之前比较路径的时候,由于当前节点可能作为最后最长路径的根,所以要把四种情况都算上
        // 但是返回的时候,是作为之后的一些结果的子集,这时候当前节点一定不能为最长路径的根节点
        // 所以要么选择左分支 root.val + leftSum,要么选择右分支 root.val + rightSum
        int chooseLeftOrRightMax = Math.max(root.val + leftSum, root.val + rightSum);
        // 收益小于0,直接舍弃
        if (chooseLeftOrRightMax < 0) {
            chooseLeftOrRightMax = 0;
        }
        return chooseLeftOrRightMax;
    }
}

 

96. 不同的二叉搜索树

如果整数1 ~ n中的 k 作为根节点值,则 1 ~ k-1 会去构建左子树,k+1 ~ n 会去构建右子树。
左子树出来的形态有a 种,右子树出来的形态有 b 种,则整个树的形态有 a∗b 种。

 题解参考: https://leetcode.cn/problems/unique-binary-search-trees/solution/shou-hua-tu-jie-san-chong-xie-fa-dp-di-gui-ji-yi-h/

class Solution {

    public int numTrees(int n) {
        // 最后要返回 dp[n]
        int dp[] = new int[n+1];

        // dp[0] 代表用 0 个数去构建左子二叉搜索树,dp[n-1] 代表用 n-1 个数(除掉根节点)去构建右子二叉搜索树
        // dp[n] = dp[0]*dp[n-1] + dp[1]*dp[n-2] + dp[2]*dp[n-3] + ..... + dp[n-2]*dp[0] + ... dp[n-1]*dp[0]
        // 一共有 i 个数,左子树用 j 个,右子树用 i-j-1 个  dp[i] = dp[j]dp[i-j-1]之和 0<=j<=i-1(左子树可以用0个到i-1个(除根节点))

        // 空树,只有一种
        dp[0] = 1;
        // 一个数字的,只有一种
        dp[1] = 1;

        // dp[0] 和 dp[1] 已知,所以从 dp[2] 开始
        for (int i=2;i<=n;i++) {
            // 左子树可以用0个到i-1个(除根节点)
            for (int j=0;j<=i-1;j++) {
                dp[i] += dp[j]*dp[i-j-1];
            }
        }
        return dp[n];
    }

}

 

104. 二叉树的最大深度

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public int maxDepth(TreeNode root) {
        return dfs(root);
    }

    public int dfs(TreeNode root) {
        if (root == null) {
            return 0;
        }
        // 左右子树每次 + 1
        int leftHeiht = dfs(root.left) + 1;
        int rightHeight = dfs(root.right) + 1;
        // 返回左右子树中较高的那个
        return leftHeiht >= rightHeight ? leftHeiht: rightHeight;
    }
}

 

剑指 Offer 68 - II. 二叉树的最近公

按官方解法:

将最近公共祖先称为 LCA
情况1: p 和 q分别在 LCA 的左右子树
  ——> lhasporq && rhasporq
情况2: p 是 q 的父节点(此时 q 在 p 的左右子树都行),那么它们俩的 LCA 就是p。
  ——> (root == p || root == q) && (lhasporq || rhasporq)

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    private TreeNode res;

    public Solution() {
        res = null;
    }

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        dfs(root, p, q);
        return res;
    }

    private boolean dfs(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null) {
            return false;
        }
        // 递归,左子树中是否有 p 或 q
        boolean lhasporq = dfs(root.left, p, q);
        // 递归,右子树中是否有 p 或 q
        boolean rhasporq = dfs(root.right, p, q);
        // 情况1:lhasporq && rhasporq p和q分别在左右子树
        // 情况2: (root == p || root == q) && (lhasporq || rhasporq) 例如 p 是 q 的父节点,那么它们俩的最近公共祖先是p。
        if ((lhasporq && rhasporq) || ((root == p || root == q) && (lhasporq || rhasporq))) {
            res = root;
        }
        // dfs递归从底向上返回,父节点的 lhasporq 和 rhasporq由子节点的推断而来
        // 如果某节点的左子节点 lhasporq 或 rhasporq,那么此节点一定 lhasporq
        // 如果某节点的右子节点 lhasporq 或 rhasporq,那么此节点一定 rhasporq
        // 如果某节点的左子节点 == p 或 == q,那么此节点一定 lhasporq
        // 如果某节点的右子节点 == p 或 == q,那么此节点一定 rhasporq
        // 综上,如果某节点的左子节点满足这四个条件中的任何一个,那么此节点一定 lhasporq,所以用 || 连接
        // 综上,如果某节点的右子节点满足这四个条件中的任何一个,那么此节点一定 rhasporq,所以用 || 连接
        return lhasporq || rhasporq || root == p || root == q;
    }
}

 

94. 二叉树的中序遍历

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    
    private List<Integer> res;

    public Solution() {
        res = new ArrayList();
    }

    public List<Integer> inorderTraversal2(TreeNode root) {
        dfs(root);
        return res;
    }

    // 递归
    private void dfs(TreeNode root) {
        if (root == null) {
            return;
        }
        dfs(root.left);
        res.add(root.val);
        dfs(root.right);
    }

    // 非递归
    public List<Integer> inorderTraversal(TreeNode root) {
        Stack<TreeNode> stack = new Stack();
        while(root != null || !stack.isEmpty()) {
            // 左节点不为空的话,就一直将左节点入栈
            if (root != null) {
                stack.push(root);
                root = root.left;
            }
            // 遇到第一个左节点为空的,就弹出它,并令 root = 它的右节点, 然后继续往下
            else {
                root = stack.pop();
                res.add(root.val);
                root = root.right;
            }
        }
        return res;
    }
}

 

 

105. 从前序与中序遍历序列构造二叉树

 递归:

前序:[ 根节点, [左子树的前序遍历结果], [右子树的前序遍历结果] ]

中序:[ [左子树的中序遍历结果], 根节点, [右子树的中序遍历结果] ]

递归函数

public TreeNode myBuildTree(int[] preorder, int[] inorder, 
        int preorder_startIndex, 
        int preorder_endIndex,
        int inorder_startIndex,
        int inorder_endIndex)

在递归函数里,先构造一个根节点 nowroot,根节点的值是已知的,就是 preorder[preorder_startIndex]

然后就可以开始下一次递归,来构造这个根节点的左右子节点了:

nowroot.left = myBuildTree(preorder, inorder, 
                preorder_startIndex+1,
                preorder_startIndex+1+leftSubSize,
                inorder_startIndex,
                inorder_startIndex+leftSubSize
                );

nowroot.right = myBuildTree(preorder, inorder,
                preorder_startIndex+1+leftSubSize,
                preorder_endIndex,
                inorder_startIndex+leftSubSize+1,
                inorder_endIndex);

我们发现 左右子树的 preorder_startIndex, preorder_endIndex, inorder_startIndex, inorder_endIndex

都可以由这个树的 preorder_startIndex, preorder_endIndex, inorder_startIndex, inorder_endIndex 再加上 leftSubSize 获得

leftSubSize 就是左子树的长度,比如说左子树的 preorder_endIndex 就为 preorder_startIndex + 1 + leftSubSize (其它的同理)

那么 leftSubSize 怎么获得呢?

起始就是 inorder 的根节点下标 减去 inorder 的起始下标,而 inorder 中的根节点下标我们是不知道的

我们已知的是 preorder 里面的根节点位置,即 preorder_startIndex,从而也知道了根节点的值

所以可以通过 根节点的值 来在 inorder 中找 inorder 中根节点的下标

每次都遍历会比较慢,所以我们在可以用一个  inorderValue2IndexMap,一开始就初始化好,后面直接就可以根据根节点的值求出根节点在 inorder 中的下标

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {

    public HashMap<Integer, Integer> inorderValue2IndexMap;

    public Solution() {
        inorderValue2IndexMap = new HashMap();
    }

    
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        int n = inorder.length;
        // 构造 inorderValue2IndexMap
        for (int i=0;i<inorder.length;i++) {
            inorderValue2IndexMap.put(inorder[i], i);
        }
        // 递归
        // 为什么这里的 endIndex 是 n 不是 n-1 呢??
        return myBuildTree(preorder, inorder, 0, n, 0, n);
    }

    // 前序:[ 根节点, [左子树的前序遍历结果], [右子树的前序遍历结果] ]
    // 中序:[ [左子树的中序遍历结果], 根节点, [右子树的中序遍历结果] ]
    public TreeNode myBuildTree(int[] preorder, int[] inorder, 
        int preorder_startIndex, 
        int preorder_endIndex,
        int inorder_startIndex,
        int inorder_endIndex) {

        // 注意返回条件:preorder 为空
        if (preorder_startIndex == preorder_endIndex) {
            return null;
        }

        // 根节点所在位置就是 preorder 的开头位置,所以根节点的值是已知的
        int preorder_nowroot_index = preorder_startIndex;
        int nowroot_val = preorder[preorder_startIndex];

        // 构造当前的根节点
        TreeNode nowroot = new TreeNode(nowroot_val);

        // 根据下面的递归可知,要求下次递归的边界条件--左右子树的 preorder_startIndex,preorder_endIndex,inorder_startIndex,inorder_endIndex
        // 只需要求出左子树的长度 leftSubSize 即可
        // 左子树的长度 leftSubSize 求法:inorder 中根节点 index - inorder 的 startIndex
        // 而 inorder 中根节点 index 需要根据根节点的值在 inorder 中找到下标,这也就是为什么要搞一个inorderValue2IndexMap
        int inorder_nowroot_index = inorderValue2IndexMap.get(nowroot_val);
        int leftSubSize = inorder_nowroot_index - inorder_startIndex;

        // 根据下面这两个,来找左右子树的 preorder_startIndex,preorder_endIndex,inorder_startIndex,inorder_endIndex
        // 只要通过这次的值再加上 leftSubSize 就可以全部知道了
        // 前序:[ 根节点, [左子树的前序遍历结果], [右子树的前序遍历结果] ]
        // 中序:[ [左子树的中序遍历结果], 根节点, [右子树的中序遍历结果] ]
        nowroot.left = myBuildTree(preorder, inorder, 
                preorder_startIndex+1,
                preorder_startIndex+1+leftSubSize,
                inorder_startIndex,
                inorder_startIndex+leftSubSize
                );

        nowroot.right = myBuildTree(preorder, inorder,
                preorder_startIndex+1+leftSubSize,
                preorder_endIndex,
                inorder_startIndex+leftSubSize+1,
                inorder_endIndex);
        
        return nowroot;
    }
}

 

 

226. 翻转二叉树

嗯嗯,没想到这么简单

直接  dfs 和交换就可以了,而且试验过, dfs 和 交换操作 的顺序并没有影响,谁在前结果都是正确的

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public TreeNode invertTree(TreeNode root) {
        dfs(root);
        return root;
    }

    public void dfs(TreeNode root) {
        if (root == null) {
            return;
        }

        TreeNode leftNode = root.left;
        TreeNode rightNode = root.right;
        root.left = rightNode;
        root.right = leftNode;

        dfs(root.left);
        dfs(root.right);
    }
}

 

101. 对称二叉树

 我们可以实现这样一个递归函数,通过「同步移动」两个指针的方法来遍历这棵树,

p 指针和 q 指针一开始都指向这棵树的根,随后

p 右移时,q 左移,

p 左移时,q 右移。

每次检查当前 p 和 q 节点的值是否相等,如果相等再判断左右子树是否对称。

话不多说,直接看代码

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public boolean isSymmetric(TreeNode root) {
        return dfs(root.left, root.right);
    }

    public boolean dfs(TreeNode p, TreeNode q) {
        // 两个都为 null ,都没有子树,那么二者肯定是对称的
        if (p == null && q == null) {
            return true;
        }
        // 前面已经排除了都为 null 的情况,这里就是一个为 null,一个不为 null 的情况,一定不对称
        if (p == null || q == null) {
            return false;
        }
        // 二者都不为 null 的情况
        // 不仅要它们两个本身相等,还要它们的左右子树分别对称
        return p.val == q.val && dfs(p.left, q.right) && dfs(p.right, q.left);
    }
}

 

98. 验证二叉搜索树

 如果只像下面第一次写的错误方法 dfs2 那样,只在每个节点判断 左<自己,右>自己 的话

那么就会出现下面这种情况

         5
        / \
       4   6
          / \
         3   7

每一个微观看都是正确的,比如 3<6 这里微观看是正确的,但是 3 比 5 小却在 5 的右边,是不对的 

所以要每次 dfs 的时候,还要定义一个当前允许的 最小值 low 和 最大值 high

boolean dfs2(TreeNode root, long low, long high)

每次递归:

  • 如果当前节点值 <=low || >=high return false
  • 之后再递归判断左右子树 dfs(root.left,low,root.val) && dfs(root.right,root.val,high)

递归左子树时,low 还是 low,但是因为左子树的所有值都要比当前根小,所以 high 变为 root.val

递归右子树时,high 还是 high,但是因为右子树的所有值都要比当前根大,所以 low 变为 root.val

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public boolean isValidBST(TreeNode root) {
        return dfs2(root, Long.MIN_VALUE, Long.MAX_VALUE);
    }

    public boolean dfs2(TreeNode root, long low, long high) {
        if (root == null) {
            return true;
        }

        // 判断当前节点,而不是判断 root.left root.right
        if (root.val <= low || root.val >= high) {
            return false;
        }

        // 当前节点符合时,且其左子树右子树也符合
        return dfs2(root.left, low, root.val) 
            && dfs2(root.right, root.val, high);
    }


    // 错误的解法,如下面这个
    /*   5
        / \
       4   6
          / \
         3   7
    */
    // 每一个微观看都是正确的,但是 3<6 这里
    // 微观看是正确的,但是 3 比 5 小却在 5 的右边,是不对的 
    public boolean dfs(TreeNode root) {
        if (root == null) {
            return true;
        }

        // 两者都为 Null
        if (root.left == null && root.right == null) {
            return true;
        }

        // 前面已经把都为 Null 的排除了,这里是 left 不为 Null, right 为 null 的
        if (root.right == null) {
            return root.left.val < root.val && dfs(root.left);
        }

        // 前面已经把都为 Null 的排除了,这里是 right 不为 Null, left 为 null 的
        if (root.left == null) {
            return root.right.val > root.val && dfs(root.right);
        }

        // 都不为 null 时
        // 当前节点符合,且其左子树右子树也符合
        return root.left.val < root.val 
            && root.right.val > root.val 
            && dfs(root.left) 
            && dfs(root.right);
    }
}

 

 

108. 将有序数组转换为二叉搜索树

 解法有点类似前面的 从前序与中序遍历中构建二叉树

也是用递归

TreeNode buildBST(int[] nums, int startIndex, int endIndex)

在递归里面,先构建一个根节点 nowroot,根节点在 nums 里的下标是 (startIndex + endIndex) / 2

然后 nowroot 的左子节点和右子节点就通过递归得到

nowRoot.left = buildBST(nums, startIndex, nowRootIndex);
nowRoot.right = buildBST(nums, nowRootIndex + 1, endIndex);
return nowRoot;
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public TreeNode sortedArrayToBST(int[] nums) {
        return buildBST(nums, 0, nums.length);
    }

    public TreeNode buildBST(int[] nums, int startIndex, int endIndex) {
        if (startIndex == endIndex) {
            return null;
        }
        int nowRootIndex = (startIndex + endIndex) / 2;
        TreeNode nowRoot = new TreeNode(nums[nowRootIndex]);
        nowRoot.left = buildBST(nums, startIndex, nowRootIndex);
        nowRoot.right = buildBST(nums, nowRootIndex + 1, endIndex);
        return nowRoot;
    }
}

 

 因为二叉搜索树和中序遍历的性质,所以二叉搜索树的中序遍历是按照键增加的顺序进行的。于是,我们可以通过中序遍历找到第 k 个最小元素。

复杂度分析

  • 时间复杂度:O(H+k),其中 H 是树的高度。在开始遍历之前,我们需要 O(H) 到达叶结点。当树是平衡树时,时间复杂度取得最小值 O(logN+k);当树是线性树(树中每个结点都只有一个子结点或没有子结点)时,时间复杂度取得最大值
  • 空间复杂度:O(H),栈中最多需要存储 H 个元素。当树是平衡树时,高度为 logN 空间复杂度取得最小值 O(logN);当树是线性树时,高度为 N 空间复杂度取得最大值 O(N)。
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {

    private static int i=0;
    private static int res=0;

    public Solution () {
        res = 0;
        i = 0;
    }

    public int kthSmallest(TreeNode root, int k) {
        // 中序遍历
        inorder(root, k);
        return res;
    }

    private void inorder(TreeNode root, int k) {
        if (root == null) {
            return;
        }
        inorder(root.left, k);
        i++;
        if (i==k) {
            res = root.val;
            return;
        }
        inorder(root.right, k);
    }
}

 

 

199. 二叉树的右视图

简单易懂题解:https://leetcode.cn/problems/binary-tree-right-side-view/solution/jian-dan-bfsdfs-bi-xu-miao-dong-by-sweetiee/

BFS:

每层的最后一个节点加入结果,就是二叉树的右视图

关键在于层序遍历怎么区分开每一层呢?

while(!queue.isEmpty()) {
            int curLayerSize = queue.size();
            // while 里面还有 for 循环,目的是每一层一个 for 循环,不同层不用混在一起
            for(int i=0;i<curLayerSize;i++) {

DFS:

我们按照 「根结点 -> 右子树 -> 左子树」 的顺序访问,就可以保证每层都是最先访问最右边的节点的。

(与先序遍历 「根结点 -> 左子树 -> 右子树」 正好相反,先序遍历每层最先访问的是最左边的节点)

结果里每个深度放一个节点,所以 res.size 就是当前的深度,与 depth 相等的时候,说明正好来到了新的一层

if (depth == res.size()) {
            res.add(root.val);
        }
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {

    private List<Integer> res;

    public Solution() {
        res = new ArrayList();
    }

    public List<Integer> rightSideView(TreeNode root) {
        dfs(root, 0);
        return res;
    }
    
    public void bfs(TreeNode root) {
        if (root == null) {
            return;
        }
        Queue<TreeNode> queue = new LinkedList();
        queue.offer(root);
        while(!queue.isEmpty()) {
            int curLayerSize = queue.size();
            // while 里面还有 for 循环,目的是每一层一个 for 循环,不同层不用混在一起
            for(int i=0;i<curLayerSize;i++) {
                TreeNode top = queue.poll();
                if (top.left != null) {
                    queue.offer(top.left);
                }
                if (top.right != null) {
                    queue.offer(top.right);
                }
                // 每层的最后一个节点就是右视图看到的
                if (i==curLayerSize-1) {
                    res.add(top.val);
                }
            }
        }
    }

    public void dfs(TreeNode root,int depth) {
        if (root == null) {
            return;
        }
        // 结果里每个深度放一个节点,所以 res.size 就是当前的深度
        if (depth == res.size()) {
            res.add(root.val);
        }
        depth++;
        // 我们按照 「根结点 -> 右子树 -> 左子树」 的顺序访问,就可以保证每层都是最先访问最右边的节点的。
        //(与先序遍历 「根结点 -> 左子树 -> 右子树」 正好相反,先序遍历每层最先访问的是最左边的节点)
        dfs(root.right, depth);
        dfs(root.left, depth);
    }


}

 

114. 二叉树展开为链表

 非递归前序遍历的同时,通过一个 pre 指针来改变指向

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public void flatten(TreeNode root) {
        if (root == null) {
            return;
        }
        Stack<TreeNode> stack = new Stack();
        stack.push(root);
        TreeNode pre = null;
        TreeNode cur = null;
        TreeNode resHead = root;
        // 非递归前序遍历
        while(!stack.isEmpty()){
            cur = stack.pop();
            // 除了第一次循环,即 cur 是根节点的时候,都会走到这里改变指向
            if (pre != null) {
                pre.right = cur;
                pre.left = null;
            }
            pre = cur;
            // 因为栈后进先出,所以右节点先入栈
            if (cur.right != null) {
                stack.push(cur.right);
            }
            if (cur.left != null) {
                stack.push(cur.left);
            }
        }
    }
}

 

 

路径和问题

112. 路径总和

root.left == null && root.right == null 判断是叶子节点的条件
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {

    private static boolean res = false;

    public Solution() {
        res = false;
    }

    public boolean hasPathSum(TreeNode root, int targetSum) {
        if (root == null) {
            return false;
        }
        dfs(root, 0, targetSum);
        return res;
    }

    private void dfs(TreeNode root, int curSum, int targetSum) {
        if (root == null) {
            return;
        }
        curSum += root.val;
        if (root.left == null && root.right == null && curSum == targetSum) {
            res = true;
        }
        dfs(root.left, curSum, targetSum);
        dfs(root.right, curSum, targetSum);
    }


}

 

113. 路径总和 II

List 不是 Integer 那种每次都 new 一个新对象的无状态类型,所以 dfs 完后得恢复现场
注意用 LinkedList,有 addLast() removeLast() 方法
恢复现场这一句是在什么时候执行?
  • 一个节点的左子节点及其子树都 dfs 完了,右子节点及其左右子树都 dfs 完了,得回到这一层的上一层,所以得清除这一层带来的影响
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {

    private static List<List<Integer>> res;

    public Solution() {
        res = new ArrayList();
    }

    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        dfs(root, 0, targetSum, new LinkedList());
        return res;
    }

    private void dfs(TreeNode root, int curSum, int targetSum, LinkedList<Integer> curPath) {
        if (root == null) {
            return;
        }
        curSum += root.val;
        // 在末尾添加
        curPath.addLast(root.val);
        // 判断为叶子节点以后
        if (root.left == null && root.right == null && curSum == targetSum) {
            res.add(new LinkedList(curPath));
            // 自己写过的错误写法,这样每次加到结果中后 curPath 会清为空
            // 这样是不对的,curPath 应该永远不清空,res 里保存的是 curPath 某些时刻的快照
            // res.add(curPath)
            // curPath = new ArrayList();
        }
        dfs(root.left, curSum, targetSum, curPath);
        dfs(root.right, curSum, targetSum, curPath);
        // List 不是 Integer 那种每次都 new 一个新对象的无状态类型,所以 dfs 完后得恢复现场// 在末尾删除
        curPath.removeLast();
    }

 

437. 路径总和 III

Map 不是 Integer 那种每次都 new 一个新对象的无状态类型,所以 dfs 完后得恢复现场

前缀和

假设 left 到 right 下标的子数组和为 k

  • nums[left...right] = k
  • preSum[right] - preSum[left] = k
  • preSum[left] = preSum[right] - k

所以要 每次到 right 的时候,找到等于 preSum[right] - k 的 preSum[left] 有多少个
用一个 map 来记录,前缀和的 count 
key: 前缀和的值
value: 前缀和为这个值的个数
注意!!!:

  • map 要放入一个初始值 {0,1}
  • 一定要先 getPreSumCount ,再把加上当前节点的 PreSum 放进 map +1(因为当前节点的和不属于当前节点的前缀和)
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {

    private static Integer res = 0;

    public Solution() {
        res = 0;
    }

    public int pathSum(TreeNode root, int targetSum) {
        // Long 是因为有极端用例
        Map<Long, Integer> preSum2CountMap = new HashMap(); 
        // 一定要提前放进去 (0, 1) 即不加前面任何数字的话,就是0
        preSum2CountMap.put(0L, 1);
        dfs(root, preSum2CountMap, 0L, targetSum);
        return res;
    }

    private void dfs(TreeNode root, Map<Long, Integer> preSum2CountMap, Long curSum, Integer targetSum) {
        if (root == null) {
            return;
        }
        curSum+=root.val;
        res += preSum2CountMap.getOrDefault(curSum - targetSum, 0);
        // 将现在的 curSum 放进 map 一定要在 map.get(preSum) 即 map.get(curSum-targetSum) 之后
        // 因为到自己这里的所有和,不属于自己的前缀和
        preSum2CountMap.put(curSum, preSum2CountMap.getOrDefault(curSum, 0)+1);
        dfs(root.left, preSum2CountMap, curSum, targetSum);
        dfs(root.right, preSum2CountMap, curSum, targetSum);
        preSum2CountMap.put(curSum, preSum2CountMap.getOrDefault(curSum, 0)-1);
    }
}

 

 

208. 实现 Trie (前缀树)

 包含三个单词 "sea","sells","she" 的 Trie 会长啥样呢?

 

 下标隐式的定义了对应的字符,同时对于末端的字符结束,要有一个 isEnd 来表示是否是一个字符的结束

根节点,就是 Trie root = this

char c = word.charAt(i);
int cindex = c - 'a';
root.children[cindex]
class Trie {

    private Trie[] children;

    private boolean isEnd; 

    public Trie() {
        // 每个下标是否有 child 表示这个字符是否有
        children = new Trie[26];
        isEnd = false;
    }
    
    public void insert(String word) {
        // 从根节点开始,根节点 = this
        Trie root = this;
        for (int i=0;i<word.length();i++) {
            char c = word.charAt(i);
            int cindex = c - 'a';
            // 往下找,没有的话就添加新节点
            if (root.children[cindex] == null){
                Trie newTrie = new Trie();
                root.children[cindex] = newTrie;
            }
            root = root.children[cindex];
        }
        // 无论最后的这个是新加的,还是以前就有的。isEnd 都要新设为 true
        root.isEnd = true;
    }
    
    public boolean search(String word) {
        Trie node = searchNode(word);
        // 有这样的节点,并且该节点是最后一个
        return node != null && node.isEnd;
    }
    
    public boolean startsWith(String prefix) {
        return searchNode(prefix) != null;
    }

    public Trie searchNode(String prefix) {
        // 从根节点开始,根节点 = this
        Trie root = this;
        for (int i=0;i<prefix.length();i++) {
            char c = prefix.charAt(i);
            int cindex = c - 'a';
            if (root.children[cindex] == null){
                return null;
            }
            // 往下找
            root = root.children[cindex];
        }
        return root;
    }
}

/**
 * Your Trie object will be instantiated and called as such:
 * Trie obj = new Trie();
 * obj.insert(word);
 * boolean param_2 = obj.search(word);
 * boolean param_3 = obj.startsWith(prefix);
 */