文章目录
94. 二叉树中序遍历
public class PreInPosTraversal {
public static class TreeNode {
public int value;
public TreeNode left;
public TreeNode right;
public TreeNode(int value) {
this.value = value;
}
}
/**
* 递归中序遍历
* @param head
*/
public static void inOrderRecur(TreeNode head, List<Integer> res) {
if (head == null) {
return;
}
inOrderRecur(head.left, res);
res.add(head.value);
inOrderRecur(head.right, res);
}
/**
* 中序遍历:左 中 右
* 先把当前节点的左边界全部压入栈中
* 如果当前节点不为空,当前节点入栈,然后往左
* 如果当前节点为空,从栈中弹出一个打印,当前节点往右
* @param head
*/
public static List<Integer> inOrderUnRecur(TreeNode head) {
ArrayList<Integer> res = new ArrayList<>();
if (head != null) {
TreeNode curNode = head;
Stack<TreeNode> stack = new Stack<>();
while (!stack.isEmpty() || curNode != null) {
// 当前节点不为空,遍历左子节点
if (curNode != null) {
stack.push(curNode);
curNode = curNode.left;
} else {
// 当前节点为空,从栈中拿出一个,往右
curNode = stack.pop();
res.add(curNode.value);
curNode = curNode.right;
}
}
}
return res;
}
}
95. 不同的二叉搜索树 II


class Solution {
public static void main(String[] args) {
Solution solution = new Solution();
solution.generateTrees(3);
}
public List<TreeNode> generateTrees(int n) {
List<TreeNode> ans = new ArrayList<>();
if (n == 0) return ans;
return buildTree(1, n);
}
private List<TreeNode> buildTree(int start, int end) {
List<TreeNode> ans = new ArrayList<>();
//此时没有数字,将 null 加入结果中
if (start > end) {
ans.add(null);
return ans;
}
//只有一个数字,当前数字作为一棵树加入结果中
if (start == end) {
TreeNode tree = new TreeNode(start);
ans.add(tree);
return ans;
}
//尝试每个数字作为根节点
for (int i = start; i <= end; i++) {
//得到所有可能的左子树
List<TreeNode> leftTrees = buildTree(start, i - 1);
//得到所有可能的右子树
List<TreeNode> rightTrees = buildTree(i + 1, end);
//左子树右子树两两组合
for (TreeNode leftTree : leftTrees) {
for (TreeNode rightTree : rightTrees) {
// 当前节点作为根节点
TreeNode root = new TreeNode(i);
// 左子树的可能结果
root.left = leftTree;
// 右子树的可能结果
root.right = rightTree;
//加入到最终结果中
ans.add(root);
}
}
}
return ans;
}
}
96. 不同的二叉搜索树


class Solution {
public static void main(String[] args) {
System.out.println(numTrees(2));
}
public static int numTrees(int n) {
if (n < 1) return 0;
// 定义dp[],dp[i] 表示i个节点一共有多少种可能
int[] dp = new int[n + 1];
dp[0] = 1;
dp[1] = 1;
// 从2开始算起,每次都会使用到前面的计算结果,直到计算到dp[n]
for (int i = 2; i <= n; i++) {
// 左子树元素的个数 从 0 ~ i-1
for (int j = 0; j < i; j++) {
dp[i] += dp[j] * dp[i - j - 1];
}
}
return dp[n];
}
}
98. 验证二叉搜索树

/**
* 中序遍历判断是否是搜索二叉树
* 中序遍历的时候,前一个节点比后一个节点小就是搜索二叉树
* @param root
* @return
*/
public boolean isValidBST(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
// 记录前一个节点的值
Integer preVal = null;
while (!stack.isEmpty() || cur != null) {
if (cur != null) {
stack.push(cur);
cur = cur.left;
} else {
cur = stack.pop();
// 如果前一个节点的值大于当前节点的值,则不是二叉搜索树
if (preVal != null && preVal >= cur.val) {
return false;
}
preVal = cur.val;
cur = cur.right;
}
}
return true;
}
101. 对称二叉树

class Solution {
public boolean isSymmetric(TreeNode root) {
if (root == null) return true;
return isSymmetricHelper(root.left, root.right);
}
private boolean isSymmetricHelper(TreeNode left, TreeNode right) {
// 左右都为 null,对称
if (left == null && right == null) return true;
// 左右有一个为 null,不对称
if (left == null || right == null) return false;
// 左右都不为null
return (left.val == right.val) && //左右值相等
isSymmetricHelper(left.left, right.right) && // 左的左子树与右的右子树对称
isSymmetricHelper(left.right, right.left); // 左的右子树与右的左子树对称
}
}
102. 二叉树的层次遍历
/**
* 思路:
* 将父节点入队列,然后弹出加入结果集,将父节点的左右子节点入队列
* 依次弹出刚加入的节点加入结果集,然后再将这两个节点的子节点加入队列
*/
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if (root == null) {
return res;
}
LinkedList<TreeNode> queue = new LinkedList<>();
//在容量已满的情况下,add()方法会抛出IllegalStateException异常,offer()方法只会返回
queue.offer(root);
while (!queue.isEmpty()) {
// 每一层的集合
ArrayList<Integer> levelArr = new ArrayList<>();
// 当前层的节点个数
int size = queue.size();
// 遍历当前层,并将当前层的子节点加入队列中
for (int i = 0; i < size; i++) {
TreeNode head = queue.poll();
// 将当前层加入到集合中
levelArr.add(head.val);
// 添加左节点
if (head.left != null) {
queue.offer(head.left);
}
// 添加右节点
if (head.right != null) {
queue.offer(head.right);
}
}
// 添加当前层所有节点
res.add(levelArr);
}
return res;
}
}
103. 二叉树的锯齿形层次遍历
/**
* 在按层遍历的基础上,遍历每一个层之后,从list中取元素方向相反
*/
class Solution {
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if (root == null) return result;
ArrayList<TreeNode> list = new ArrayList<>();
boolean leftToRight = true;
list.add(root);
while (!list.isEmpty()) {
int size = list.size();
ArrayList<Integer> levelList = new ArrayList<>();
// 如果是从左往右
if (leftToRight) {
for (int i = 0; i < size; i++) {
TreeNode curr = list.remove(0);
levelList.add(curr.val);
// 先加左子节点
if (curr.left != null) list.add(curr.left);
// 再加右子节点
if (curr.right != null) list.add(curr.right);
}
} else { // 从右往左
for (int i = 0; i < size; i++) {
// 取出最后一个
TreeNode curr = list.remove(list.size() - 1);
levelList.add(curr.val);
// 将右子节点加入到list头部
if (curr.right != null) list.add(0, curr.right);
// 再将左节点加入到list的头部
if (curr.left != null) list.add(0, curr.left);
}
}
result.add(levelList);
leftToRight = !leftToRight;
}
return result;
}
}
104. 二叉树的最大深度
class Solution {
public int maxDepth(TreeNode root) {
if (root == null) {
return 0;
}
// 左子树的深度
int left = maxDepth(root.left);
// 右子树的深度
int right = maxDepth(root.right);
// 左右子树深度的较大值 加上当前节点
return Math.max(left, right) + 1;
}
}
105. 从前序与中序遍历序列构造二叉树
class Solution {
public static void main(String[] args) {
int[] pre = {3, 9, 20, 15, 7};
int[] in = {9, 3, 15, 20, 7};
Solution solution = new Solution();
solution.buildTree(pre, in);
}
public TreeNode buildTree(int[] preorder, int[] inorder) {
if (preorder == null || inorder == null || preorder.length == 0 || preorder.length != inorder.length)
return null;
return buildTreeHelper(preorder, inorder, 0, 0, preorder.length - 1);
}
private TreeNode buildTreeHelper(int[] preorder, int[] inorder, int preSt, int inSt, int inEnd) {
if (preSt > preorder.length || inSt > inEnd) return null;
TreeNode current = new TreeNode(preorder[preSt]);
int i = inSt;
// 找到preorder的第一个数(整颗树根节点)在中序遍历中的位置
// 该位置的左边就是整颗数的左子树,右边是整颗树的右子树
while (i <= inEnd) {
if (inorder[i] == preorder[preSt]) break;
i++;
}
// 对于左子树:根节点的起始位置为 preSt+1,中序遍历的起始位置为isSt,结束位置为 i-1
current.left = buildTreeHelper(preorder, inorder, preSt + 1, inSt, i - 1);
// 对于右子树:根节点起始位置为,当前根节点上左子树节点的值,右子树的中序遍历起始位置为 i+1,结束位置为 inEnd
current.right = buildTreeHelper(preorder, inorder, preSt + (i - inSt + 1), i + 1, inEnd);
return current;
}
}

106. 从中序与后序遍历序列构造二叉树
class Solution {
public static void main(String[] args) {
int[] pre = {3, 9, 20, 15, 7};
int[] in = {9, 3, 15, 20, 7};
Solution solution = new Solution();
solution.buildTree(pre, in);
}
public TreeNode buildTree(int[] postorder, int[] inorder) {
if (postorder == null || inorder == null || postorder.length == 0 || postorder.length != inorder.length)
return null;
return buildTreeHelper(postorder, inorder, postorder.length - 1, 0, inorder.length - 1);
}
private TreeNode buildTreeHelper(int[] postorder, int[] inorder, int postEnd, int inStart, int inEnd) {
if (inStart > inEnd) return null;
TreeNode current = new TreeNode(postorder[postEnd]);
int i = 0;
while (i <= inEnd) {
if (inorder[i] == postorder[postEnd]) break;
i++;
}
current.left = buildTreeHelper(postorder, inorder, postEnd - (inEnd - i) - 1, inStart, i - 1);
current.right = buildTreeHelper(postorder, inorder, postEnd - 1, i + 1, inEnd);
return current;
}
}

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

class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
return this.sortedArrayToBSTHelper(nums, 0, nums.length - 1);
}
/**
* @param nums 有序数组
* @param start 数组起始下标
* @param end 数组结束下标
* @return
*/
public TreeNode sortedArrayToBSTHelper(int[] nums, int start, int end) {
if (start > end) return null;
int mid = (start + end) / 2;
// 根节点为数组中点的数
TreeNode root = new TreeNode(nums[mid]);
// 左边的形成左子树
root.left = sortedArrayToBSTHelper(nums, start, mid - 1);
// 右边的形成右子树
root.right = sortedArrayToBSTHelper(nums, mid + 1, end);
return root;
}
}
110. 平衡二叉树判断
class Solution {
public boolean isBalanced(TreeNode root) {
// 如果返回的是高度,不是 -1 就是平衡的,返回 -1就是不平衡的
return maxDepth(root) != -1;
}
public int maxDepth(TreeNode root) {
if (root == null) {
return 0;
}
int left = maxDepth(root.left);
int right = maxDepth(root.right);
// 左子树返回 -1,或者右子树返回 -1,或者左右高度差大于 1,都不平衡
if (left == -1 || right == -1 || Math.abs(left - right) > 1) {
// 定义返回-1,表示不平衡
return -1;
}
// 返回高度
return Math.max(left, right) + 1;
}
}
111. 二叉树的最小深度
// recursion
class Solution {
public int minDepth(TreeNode root) {
if (root == null) {
return 0;
}
if (root.left == null && root.right == null) {
return 1;
}
int minDepth = Integer.MAX_VALUE;
// 左子树的最小深度
if (root.left != null) {
minDepth = Math.min(minDepth(root.left), minDepth);
}
// 右子树的最小深度
if (root.right != null) {
minDepth = Math.min(minDepth(root.right), minDepth);
}
// 子树的最小深度加上当前节点的深度就是当前树的最小深度
return minDepth + 1;
}
}
时间复杂度:我们访问每个节点一次,时间复杂度为 O(N) ,其中 N 是节点个数。
空间复杂度:最坏情况下,整棵树是非平衡的,例如每个节点都只有一个孩子,递归会调用 N (树的高度)次,
因此栈的空间开销是 O(N) 。但在最好情况下,树是完全平衡的,高度只有 log(N),
因此在这种情况下空间复杂度只有 O(log(N)) 。
// iteration
class Solution {
public int minDepth(TreeNode root) {
LinkedList<Pair<TreeNode, Integer>> stack = new LinkedList<>();
if (root == null) {
return 0;
}
else {
stack.add(new Pair(root, 1));
}
int min_depth = Integer.MAX_VALUE;
while (!stack.isEmpty()) {
Pair<TreeNode, Integer> current = stack.pollLast();
root = current.getKey();
int current_depth = current.getValue();
if ((root.left == null) && (root.right == null)) {
min_depth = Math.min(min_depth, current_depth);
}
if (root.left != null) {
stack.add(new Pair(root.left, current_depth + 1));
}
if (root.right != null) {
stack.add(new Pair(root.right, current_depth + 1));
}
}
return min_depth;
}
}
时间复杂度:每个节点恰好被访问一遍,复杂度为 O(N)。
空间复杂度:最坏情况下我们会在栈中保存整棵树,此时空间复杂度为 O(N)。
124. 二叉树的最大路径和
public class Solution {
public static void main(String[] args) {
TreeNode root = new TreeNode(-1);
TreeNode left = new TreeNode(-2);
TreeNode right = new TreeNode(-3);
root.left = left;
root.right = right;
System.out.println(maxPathSum(root));
}
public static int maxPathSum(TreeNode root) {
ResultType result = helper(root);
return result.maxPath;
}
private static class ResultType {
// singlePath: 从root往下走到任意点的最大路径,这条路径可以不包含任何点
// maxPath: 从树中任意到任意点的最大路径,这条路径至少包含一个点
int singlePath, maxPath;
ResultType(int singlePath, int maxPath) {
this.singlePath = singlePath;
this.maxPath = maxPath;
}
}
private static ResultType helper(TreeNode root) {
if (root == null) {
return new ResultType(0, Integer.MIN_VALUE);
}
// Divide
ResultType left = helper(root.left);
ResultType right = helper(root.right);
// Conquer
//左子树单向路径最大和 加上右子树单向路径最大和 再加上根节点
int singlePath = Math.max(left.singlePath, right.singlePath) + root.val;
//singlePath是负数,则让其为0
singlePath = Math.max(singlePath, 0);
int maxPath = Math.max(left.maxPath, right.maxPath);
maxPath = Math.max(maxPath, left.singlePath + right.singlePath + root.val);
return new ResultType(singlePath, maxPath);
}
}
144. 二叉树前序遍历
public class PreInPosTraversal {
public static class TreeNode {
public int value;
public TreeNode left;
public TreeNode right;
public TreeNode(int value) {
this.value = value;
}
}
/**
* 递归前序遍历
* @param head
*/
public static void preOrderRecur(TreeNode head, List<Integer> res) {
if (head == null) {
return;
}
res.add(head.value);
preOrderRecur(head.left, res);
preOrderRecur(head.right, res);
}
/**
* 采用分治的思想解决前序遍历
* 注意和递归版本的区别,有无返回值
* @param root
* @return
*/
public ArrayList<Integer> preorderTraversal(TreeNode root) {
ArrayList<Integer> result = new ArrayList<>();
// null or leaf
if (root == null) {
return result;
}
// Divide
ArrayList<Integer> left = preorderTraversal(root.left);
ArrayList<Integer> right = preorderTraversal(root.right);
// Conquer
result.add(root.value);
result.addAll(left);
result.addAll(right);
return result;
}
/**
* 前序遍历:中 左 右
* 压头节点,然后先压右再压左(没有就不压)
* 因为每一次要弹出一个所以要先压右,再压左,左先出,右后出
* @param head
*/
public static List<Integer> preOrderUnRecur(TreeNode head) {
ArrayList<Integer> res = new ArrayList<>();
if (head != null) {
Stack<TreeNode> stack = new Stack<>();
// 首先将父节点入栈
stack.add(head);
// 当栈不为空的时候
while (!stack.isEmpty()) {
// 最先弹出的是父节点
TreeNode curNode = stack.pop();
res.add(curNode.value);
// 前序遍历,左在右的前面,右子节点先入栈
if (curNode.right != null) {
stack.push(curNode.right);
}
if (curNode.left != null) {
stack.push(curNode.left);
}
}
}
return res;
}
}
145. 二叉树后序遍历
public class PreInPosTraversal {
public static class TreeNode {
public int value;
public TreeNode left;
public TreeNode right;
public TreeNode(int value) {
this.value = value;
}
}
/**
* 递归后续遍历
* @param head
*/
public static void posOrderRecur(TreeNode head, List<Integer> res) {
if (head == null) {
return;
}
posOrderRecur(head.left, res);
posOrderRecur(head.right, res);
res.add(head.value);
}
/**
* 后序遍历
* 前序是: 中 左 右 ——> 改为: 中 右 左 即先压左,再压右
* 这时候上面的出栈顺序就是 中 右 左,出栈后再入另一个栈
* 然后再出栈就是:左 右 中 的顺序,即后续
* @param head
*/
public static List<Integer> posOrderUnRecur1(TreeNode head) {
ArrayList<Integer> res = new ArrayList<>();
if (head != null) {
Stack<TreeNode> helpStack = new Stack<>();
Stack<TreeNode> postStack = new Stack<>();
helpStack.push(head);
while (!helpStack.isEmpty()) {
// 出栈
TreeNode curNode = helpStack.pop();
// 这个时机就是前序遍历的打印,我们在这里放入栈中
//System.out.print(curNode.name + " ");
// 放入到另一个栈中
postStack.push(curNode);
// 先向左再向右,出栈就是 中 右 左
if (curNode.left != null) {
helpStack.push(curNode.left);
}
if (curNode.right != null) {
helpStack.push(curNode.right);
}
}
while (!postStack.isEmpty()) {
// 此时从postStack出栈就是:左 右 中
res.add(postStack.pop().value);
}
}
return res;
}
}
199. 二叉树的右视图
/**
* https://www.youtube.com/watch?v=f72I2qz9K7k
*/
class Solution {
/**
* dfs
* @param root
* @return
* 时空复杂度:O(n)
*/
public List<Integer> rightSideView1(TreeNode root) {
ArrayList<Integer> res = new ArrayList<>();
if (root == null) return res;
dfs(root, res, 0);
return res;
}
private void dfs(TreeNode root, ArrayList<Integer> res, int level) {
if (root == null) return;
// 因为我们的 level是从0层开始的,所以当 level=size的时候
// 此时加入的节点就是进入该层的第一个元素(该层右边的第一个节点)
if (level == res.size()) res.add(root.val);
dfs(root.right, res, level + 1);
dfs(root.left, res, level + 1);
}
/**
* bfs(按层遍历,遍历到每一层,将当前层的最后一个元素加入到结果集中)
* @param root
* @return
* 时空复杂度:O(n)
*/
public List<Integer> rightSideView2(TreeNode root) {
ArrayList<Integer> res = new ArrayList<>();
if (root == null) return res;
LinkedList<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
// 遍历当前层
for (int i = 0; i < size; i++) {
TreeNode curr = queue.poll();
// 当前层的最后一个
if (i == size - 1) {
res.add(curr.val);
}
if (curr.left !=null) queue.offer(curr.left);
if (curr.right != null) queue.offer(curr.right);
}
}
return res;
}
}
222. 完全二叉树的节点个数
/**
* 完全二叉树:每一个层从左往右依次填满,只有最后一层可以不满
* 先遍历左边界,能得到整个二叉树的高度
* 然后遍历根节点右子树的左边界
* 如果到了最后一层,则根节点的左子树是满二叉树,可以根据层数求出节点个数
* 右子树又是一个完全二叉树,递归求右子树的节点个数
* 如果没有到最后一层,则此时右子树为满二叉树,只是高度比整颗数少一层
* 左子树又是一个完全二叉树,递归求右子树的节点个数
* <p>
* 每一次遍历当前节点右子节点的左边界,所以每一层遍历一个节点到底层
* 遍历每一层O(logN),然后每一层遍历到底层又是O(logN),所以最后是O((logN)^2)
*/
class Solution {
public int countNodes(TreeNode root) {
if (root == null) {
return 0;
}
return bs(root, 1, mostLeftLevel(root, 1));
}
/**
* @param node 当前节点
* @param level 当前节点在第几层
* @param h 整个树的深度
* @return 返回以node为头节点的树的节点个数
*/
public int bs(TreeNode node, int level, int h) {
// node在level层,level == h 则node在最后一层,即树只有一个节点
if (level == h) {
return 1;
}
// 判断 node右子树的深度是否到达整颗树的最底层
if (mostLeftLevel(node.right, level + 1) == h) {
// 如果到达了最底层,则左子树为满二叉树,整颗树的高度为 h,则左子树的高度为 h-level
// 左子树的节点个数为 2^(h-level) - 1
// 则除了右子树外的节点总个数为,左子树节点 + 根节点 2^(h-level) - 1 + 1 = 2^(h-level)
// 1 << (h - level):左子树加上头结点的总个数,然后递归求右子树的节点个数
return (1 << (h - level)) + bs(node.right, level + 1, h);
} else {
// 右子树的深度没有到达整颗树的最底层
// 节点个数为:右子树加上头结点的总个数(1 << (h - level - 1)) + 递归求左子树
return (1 << (h - level - 1)) + bs(node.left, level + 1, h);
}
}
/**
* 以当前节点为根节点的树到达了整颗树的哪一层
* 比如当前节点在第 3 (level = 3)层,返回值是 5,则表示当前树延伸到整颗树的第 5层
*
* @param node 当前节点
* @param level 当前节点在树中的哪一层(从1开始)
* @return
*/
public int mostLeftLevel(TreeNode node, int level) {
while (node != null) {
level++;
node = node.left;
}
return level - 1;
}
}
六、完全二叉树的判断
思路:按层遍历
(1) 任何一个节点有右子节点无左子节点返回false
(2) 任何一个节点,当其左右两个子节点不是双全的时候(有左无右,都无),后面遍历到的所有节点都必须是叶子节点,否则返回false

public static boolean isCBT(TreeNode head) {
if (head == null) {
return true;
}
Queue<TreeNode> queue = new LinkedList<>();
// 标识是否开启判断叶节点的阶段
boolean leaf = false;
TreeNode leftTreeNode;
TreeNode rightTreeNode;
queue.offer(head);
// 队列不为空的时候
while (!queue.isEmpty()) {
// 获取父节点
head = queue.poll();
// 左子节点
leftTreeNode = head.left;
// 右子节点
rightTreeNode = head.right;
// (1)在开始判断叶子节点后,遍历到的节点必须左右节点为null,有一个不为null返回false
if ((leaf && (leftTreeNode != null || rightTreeNode != null)) ||
// (2)左节点为空,右节点不为空直接返回false
(leftTreeNode == null && rightTreeNode != null)) {
return false;
}
// 左节点不为空
if (leftTreeNode != null) {
queue.offer(leftTreeNode);
}
// 右节点不为空
if (rightTreeNode != null) {
queue.offer(rightTreeNode);
}
// 当左、右子节点都为空的时候,则要开始判断后面的节点是否是叶子结点
if (leftTreeNode == null || rightTreeNode == null) {
leaf = true;
}
}
return true;
}
九、二叉树的所有路径
/**
* 在递归遍历二叉树时,需要考虑当前的节点和它的孩子节点。
* 如果当前的节点不是叶子节点,则在当前的路径末尾添加该节点,并递归遍历该节点的每一个孩子节点。
* 如果当前的节点是叶子节点,则在当前的路径末尾添加该节点后,就得到了一条从根节点到叶子节点的路径,
* 可以把该路径加入到答案中。
*/
class Solution {
public List<String> binaryTreePaths(TreeNode root) {
LinkedList<String> paths = new LinkedList();
constructPaths(root, "", paths);
return paths;
}
public void constructPaths(TreeNode root, String path, LinkedList<String> paths) {
if (root != null) {
// 更新路径
path += Integer.toString(root.val);
// 当前节点是叶子节点,当前路径结束
if ((root.left == null) && (root.right == null))
// 把路径加入到答案中
paths.add(path);
else {
// 当前节点不是叶子节点,继续递归遍历
path += "->";
constructPaths(root.left, path, paths);
constructPaths(root.right, path, paths);
}
}
}
}
2509

被折叠的 条评论
为什么被折叠?



