- 1.二叉树的递归遍历
- 3. 二叉树的层序遍历
- 4. 翻转二叉树">4. 翻转二叉树
- 5. 对称二叉树">5. 对称二叉树
- 6. 二叉树的最大深度">6. 二叉树的最大深度
- 7. 二叉树的最小深度
- 8. 完全二叉树的节点个数">8. 完全二叉树的节点个数
- 9. 平衡二叉树">9. 平衡二叉树
- 10. 二叉树的所有路径">10. 二叉树的所有路径
- 11.左叶子节点之和">11.左叶子节点之和
- 12. 找树左下角的值
- 13. 路径总和
- 14. 路径总和Ⅱ">14. 路径总和Ⅱ
- 15.根据遍历结果构造二叉树
- 16. 最大二叉树">16. 最大二叉树
- 17.合并二叉树">17.合并二叉树
- 18.二叉搜索树中的搜索">18.二叉搜索树中的搜索
- 19.验证二叉搜索树">19.验证二叉搜索树
- 20.二叉搜索树的最小绝对差">20.二叉搜索树的最小绝对差
- 21.二叉搜索树中的众数">21.二叉搜索树中的众数
- 22.二叉树的最近公共祖先⭐
- 23.二叉搜索树的最近公共祖先">23.二叉搜索树的最近公共祖先
- 450.删除二叉搜索树中的节点⭐
- 669.修剪二叉搜索树⭐
- 108.将有序数组转换为二叉搜索树">108.将有序数组转换为二叉搜索树
- 538.把二叉搜索树转换为累加树⭐
1.二叉树的递归遍历
- 144. 二叉树的前序遍历
```java
public List
preorderTraversal(TreeNode root) { List ans = new ArrayList<>(); process(root, ans); return ans; }
private void process(TreeNode root, List
- [145. 二叉树的后序遍历](https://leetcode-cn.com/problems/binary-tree-postorder-traversal/)```javapublic List<Integer> postorderTraversal(TreeNode root) {List<Integer> ans = new ArrayList<>();process(root, ans);return ans;}private void process(TreeNode root, List<Integer> ans) {if (root == null) {return;}process(root.left, ans);process(root.right, ans);ans.add(root.val);}
- 94. 二叉树的中序遍历
```java
public List
inorderTraversal(TreeNode root) { ArrayList list = new ArrayList<>(); process(root, list); return list; }
private void process(TreeNode node, List
<a name="Tq3nc"></a># 2. 二叉树的迭代遍历<a name="u1Hgy"></a>## 前序遍历(迭代)- [144. 二叉树的前序遍历](https://leetcode-cn.com/problems/binary-tree-preorder-traversal/)前序遍历是中左右,每次先处理的是中间节点,那么先将根节点放入栈中,然后将右孩子加入栈,再加入左孩子。<br />为什么要先加入 右孩子,再加入左孩子呢? 因为这样出栈的时候才是中左右的顺序。<br />[【来源:代码随想录】]()<br />(**注意代码中空节点不入栈**)```java// 前序遍历顺序:中-左-右,入栈顺序:中-右-左public List<Integer> preorderTraversal(TreeNode root) {List<Integer> ans = new ArrayList<>();if (root == null) {return ans;}Stack<TreeNode> stack = new Stack<>();stack.push(root);while (!stack.isEmpty()) {TreeNode node = stack.pop();ans.add(node.val);if (node.right != null) {stack.push(node.right);}if (node.left != null) {stack.push(node.left);}}return ans;}
前序遍历迭代的代码为何能如此简洁?
其实我们有两个操作:
- 处理:将元素放进result数组中
- 访问:遍历节点
前序遍历的顺序是中左右,先访问的元素是中间节点,要处理的元素也是中间节点,所以才能写出简洁的代码,因为要访问的元素和要处理的元素顺序是一致的,都是中间节点。
中序遍历(迭代)
中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了处理顺序和访问顺序是不一致的。
那么在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。
【来源:代码随想录】
public List<Integer> inorderTraversal(TreeNode root) {List<Integer> ans = new ArrayList<>();if (root == null) {return ans;}Stack<TreeNode> stack = new Stack<>();TreeNode cur = root;while (cur != null || !stack.isEmpty()) {if (cur != null) {stack.push(cur);cur = cur.left;} else {cur = stack.pop();ans.add(cur.val);cur = cur.right;}}return ans;}
后序遍历(迭代)
145. 二叉树的后序遍历
先序遍历是中左右,后续遍历是左右中,那么我们只需要调整一下先序遍历的代码顺序,就变成中右左的遍历顺序,然后在反转result数组,输出的结果顺序就是左右中了,如下图:
所以后序遍历只需要前序遍历的代码稍作修改就可以了
// 后序遍历顺序 左-右-中 入栈顺序:中-左-右 出栈顺序:中-右-左, 最后翻转结果public List<Integer> postorderTraversal(TreeNode root) {List<Integer> ans = new ArrayList<>();if (root == null) {return ans;}Stack<TreeNode> stack = new Stack<>();stack.push(root);while (!stack.isEmpty()) {TreeNode node = stack.pop();ans.add(node.val);if (node.left != null) {stack.push(node.left);}if (node.right != null) {stack.push(node.right);}}//翻转Collections.reverse(ans);return ans;}
总结
可以看出前序和中序是完全两种代码风格,并不像递归写法那样代码稍做调整,就可以实现前后中序。 这是因为前序遍历中访问节点(遍历节点)和处理节点(将元素放进result数组中)可以同步处理,但是中序就无法做到同步! 那么问题又来了,难道 二叉树前后中序遍历的迭代法实现,就不能风格统一么(即前序遍历 改变代码顺序就可以实现中序 和 后序)? 当然可以,这种写法,还不是很好理解
3. 二叉树的层序遍历
102. 二叉树的层序遍历
层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树。这种遍历的方式和我们之前讲过的都不太一样。
需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而是用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。
而这种层序遍历方式就是图论中的广度优先遍历,只不过我们应用在二叉树上。
【来源:代码随想录】
public List<List<Integer>> levelOrder(TreeNode root) {List<List<Integer>> ans = new ArrayList<>();if (root == null) {sreturn ans;}Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);while (!queue.isEmpty()) {int size = queue.size();List<Integer> list = new ArrayList<>();// 这里一定要使用固定大小size,不要使用queue.size(),因为que.size是不断变化的for (int i = 0; i < size; i++) {TreeNode node = queue.poll();list.add(node.val);if (node.left != null) {queue.offer(node.left);}if (node.right != null) {queue.offer(node.right);}}ans.add(list);}return ans;}
107. 二叉树的层序遍历Ⅱ
相对于102.二叉树的层序遍历,就是最后把result数组反转一下就可以了。
public List<List<Integer>> levelOrderBottom(TreeNode root) {List<List<Integer>> ans = new ArrayList<>();if (root == null) {return ans;}Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);while (!queue.isEmpty()) {int size = queue.size();List<Integer> list = new ArrayList<>();for (int i = 0; i < size; i++) {TreeNode node = queue.poll();list.add(node.val);if (node.left != null) {queue.add(node.left);}if (node.right != null) {queue.add(node.right);}}ans.add(list);}Collections.reverse(ans);return ans;}
199. 二叉树的右视图
层序遍历的时候,判断是否遍历到单层的最后面的元素,如果是,就放进result数组中,随后返回result就可以了。
public List<Integer> rightSideView(TreeNode root) {List<Integer> ans = new ArrayList<>();if (root == null) {return ans;}Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);while (!queue.isEmpty()) {int size = queue.size();for (int i = 0; i < size; i++) {TreeNode node = queue.poll();if (node.left != null) {queue.offer(node.left);}if (node.right != null) {queue.offer(node.right);}if (i == size - 1) {ans.add(node.val);}}}return ans;}
637. 二叉树的层平均值
本题就是层序遍历的时候把一层求个总和在取一个均值
public List<Double> averageOfLevels(TreeNode root) {List<Double> ans = new ArrayList<>();if (root == null) {return ans;}Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);while (!queue.isEmpty()) {int size = queue.size();double sum = 0;for (int i = 0; i < size; i++) {TreeNode node = queue.poll();sum += node.val;if (node.left != null) {queue.offer(node.left);}if (node.right != null) {queue.offer(node.right);}}sum /= size;ans.add(sum);}return ans;}
429. N叉树的层序遍历
这道题依旧是模板题,只不过一个节点有多个孩子了
public List<List<Integer>> levelOrder(Node root) {List<List<Integer>> ans = new ArrayList<>();if (root == null) {return ans;}Queue<Node> queue = new LinkedList<>();queue.offer(root);while (!queue.isEmpty()) {int size = queue.size();List<Integer> list = new ArrayList<>();for (int i = 0; i < size; i++) {Node node = queue.poll();list.add(node.val);for (Node child : node.children) {queue.offer(child);}if (i == size - 1) {ans.add(list);}}}return ans;}class Node {public int val;public List<Node> children;public Node() {}public Node(int _val) {val = _val;}public Node(int _val, List<Node> _children) {val = _val;children = _children;}}
515. 在每个树行中找最大值
层序遍历,取每一层的最大值
public List<Integer> largestValues(TreeNode root) {List<Integer> ans = new ArrayList<>();if (root == null) {return ans;}Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);while (!queue.isEmpty()) {int size = queue.size();int max = Integer.MIN_VALUE;for (int i = 0; i < size; i++) {TreeNode node = queue.poll();max = Math.max(max, node.val);if (node.left != null) {queue.offer(node.left);}if (node.right != null) {queue.offer(node.right);}}ans.add(max);}return ans;}
116. 填充每个节点的下一个右侧节点指针
public Node connect(Node root) {if (root == null) {return root;}Queue<Node> queue = new LinkedList<>();queue.offer(root);while (!queue.isEmpty()) {int size = queue.size();for (int i = 0; i < size; i++) {Node node = queue.poll();if (node.left != null) {queue.offer(node.left);}if (node.right != null) {queue.offer(node.right);}if (i == size - 1) {node.next = null;} else {node.next = queue.peek();}}}return root;}
104. 二叉树的最大深度
法一:层序遍历
public int maxDepth(TreeNode root) {if (root == null) {return 0;}int ans = 0;Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);while (!queue.isEmpty()) {int size = queue.size();for (int i = 0; i < size; i++) {TreeNode node = queue.poll();if (node.left != null) {queue.offer(node.left);}if (node.right != null) {queue.offer(node.right);}if (i == size - 1) {ans++;}}}return ans;}
法二:递归
public int maxDepth(TreeNode root) {if (root == null) {return 0;}return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;}
111.二叉树的最小深度
public int minDepth(TreeNode root) {int ans = 0;if (root == null) {return ans;}Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);int level = 0;while (!queue.isEmpty()) {int size = queue.size();level++;for (int i = 0; i < size; i++) {TreeNode node = queue.poll();if (node.left != null) {queue.offer(node.left);}if (node.right != null) {queue.offer(node.right);}if (node.left == null && node.right == null) {ans = level;return ans;}}}return ans;}
4. 翻转二叉树
前序遍历(递归) ```java // 前序遍历(递归) public TreeNode invertTree(TreeNode root) { if (root == null) {
return root;
} preOrder(root); return root; }
private void preOrder(TreeNode root) { if (root == null) { return; } TreeNode tmp = root.left; root.left = root.right; root.right = tmp; preOrder(root.left); preOrder(root.right); }
- 前序遍历(迭代)```javapublic TreeNode invertTree(TreeNode root) {if (root == null) {return root;}Stack<TreeNode> stack = new Stack<>();stack.push(root);while (!stack.isEmpty() ) {TreeNode node = stack.pop();TreeNode tmp = node.left;node.left = node.right;node.right = tmp;if (node.right != null) {stack.push(node.right);}if (node.left != null) {stack.push(node.left);}}return root;}
- 层序遍历
// 层序遍历public TreeNode invertTree(TreeNode root) {if (root == null) {return root;}Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);while (!queue.isEmpty()) {TreeNode node = queue.poll();TreeNode tmp = node.left;node.left = node.right;node.right = tmp;if (node.left != null) {queue.offer(node.left);}if (node.right != null) {queue.offer(node.right);}}return root;}
5. 对称二叉树
递归法
首先想清楚,判断对称二叉树要比较的是哪两个节点,要比较的可不是左右节点!
对于二叉树是否对称,要比较的是根节点的左子树与右子树是不是相互翻转的,理解这一点就知道了其实我们要比较的是两个树(这两个树是根节点的左右子树),所以在递归遍历的过程中,也是要同时遍历两棵树。
那么如果比较呢?
比较的是两个子树的里侧和外侧的元素是否相等。如图所示:
【来源:代码随想录】
那么遍历的顺序应该是什么样的呢?
本题遍历只能是“后序遍历”,因为我们要通过递归函数的返回值来判断两个子树的内侧节点和外侧节点是否相等。
正是因为要遍历两棵树而且要比较内侧和外侧节点,所以准确的来说是一个树的遍历顺序是左右中,一个树的遍历顺序是右左中。
但都可以理解算是后序遍历,尽管已经不是严格上在一个树上进行遍历的后序遍历了。 ```java public boolean isSymmetric(TreeNode root) {
}return compare(root.left, root.right);
private boolean compare(TreeNode left, TreeNode right) { if (left == null && right != null) { return false; } else if (left != null && right == null) { return false; } else if (left == null && right == null) { return true; } else if (left.val != right.val) { // left right都不为空 return false; } else { //比较外侧 boolean outside = compare(left.left, right.right); //比较内测 boolean inside = compare(left.right, right.left); return outside && inside; } }
<a name="DYj8s"></a>## 迭代法这道题目我们也可以使用迭代法,但要注意,这里的迭代法可不是前中后序的迭代写法,因为本题的本质是判断两个树是否是相互翻转的,其实已经不是所谓二叉树遍历的前中后序的关系了。<br />这里我们可以使用队列来比较两个树(根节点的左右子树)是否相互翻转,(**注意这不是层序遍历**)使用队列<br />通过队列来判断根节点的左子树和右子树的内侧和外侧是否相等,如动画所示:<br /><br />【from代码随想录】<br />如下的条件判断和递归的逻辑是一样的。```java// 使用队列法public boolean isSymmetric2(TreeNode root) {Queue<TreeNode> queue = new LinkedList<>();queue.offer(root.left);queue.offer(root.right);while (!queue.isEmpty()) {TreeNode left = queue.poll();TreeNode right = queue.poll();if (left == null && right == null) {continue;} else if (left == null || right == null || left.val != right.val) {return false;}queue.offer(left.left);queue.offer(right.right);queue.offer(left.right);queue.offer(right.left);}return true;}
类似题目
100.相同的树
- 递归做法 ```java
public boolean isSameTree(TreeNode p, TreeNode q) { return compare(p, q); }
private boolean compare(TreeNode left, TreeNode right) { if (left == null && right == null) { return true; } else if (left == null || right == null || left.val != right.val){ return false; } return compare(left.left, right.left) && compare(left.right, right.right); }
- 队列做法```javapublic boolean isSameTree2(TreeNode p, TreeNode q) {Queue<TreeNode> queue = new LinkedList<>();queue.offer(p);queue.offer(q);while (!queue.isEmpty()) {TreeNode node1 = queue.poll();TreeNode node2 = queue.poll();if (node1 == null && node2 == null) {continue;} else if (node1 == null || node2 == null || node1.val != node2.val) {return false;}queue.offer(node1.left);queue.offer(node2.left);queue.offer(node1.right);queue.offer(node2.right);}return true;}
572.另一棵树的子树
public boolean isSubtree(TreeNode s, TreeNode t) {// 迭代的中序遍历Stack<TreeNode> stack = new Stack<>();TreeNode cur = s;while (cur != null || !stack.isEmpty()) {if (cur != null) {stack.push(cur);cur = cur.left;} else {cur = stack.pop();if (compare(cur, t)) {return true;}cur = cur.right;}}return false;}private boolean compare(TreeNode node1, TreeNode node2) {if (node1 == null && node2 == null) {return true;} else if (node1 == null || node2 == null || node1.val != node2.val){return false;}return compare(node1.left, node2.left) && compare(node1.right, node2.right);}
6. 二叉树的最大深度
-
递归法
本题可以使用前序(中左右),也可以使用后序遍历(左右中),使用前序求的就是深度,使用后序求的是高度。
而根节点的高度就是二叉树的最大深度,所以本题中我们通过后序求的根节点高度来求的二叉树最大深度。 后序做法 ```java //后序求root的高度,就是整棵树的深度 public int maxDepth(TreeNode root) { return getHeight(root); }
private int getHeight(TreeNode node) { if (node == null) { return 0; } int leftHeight = getHeight(node.left); int rightHeight = getHeight(node.right); return Math.max(leftHeight, rightHeight) + 1; }
- 本题当然也可以使用前序,代码如下:(**充分表现出求深度回溯的过程**)```java// 真正的求root的深度public int result = 0;public int maxDepth2(TreeNode root) {if (root == null) {return result;}getDepth(root, 1);return result;}private void getDepth(TreeNode node, int depth) {result = Math.max(result, depth); // 中if (node.left == null && node.right == null) {return;}if (node.left != null) { // 左getDepth(node.left, depth + 1);}if (node.right != null) { // 右getDepth(node.right, depth + 1);}return;}
可以看出使用了前序(中左右)的遍历顺序,这才是真正求深度的逻辑!
迭代法
// 法三:迭代:层序遍历public int maxDepth3(TreeNode root) {if (root == null) {return 0;}Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);int depth = 0;while (!queue.isEmpty()) {int size = queue.size();for (int i = 0; i < size; i++) {TreeNode node = queue.poll();if (node.left != null) {queue.offer(node.left);}if (node.right != null) {queue.offer(node.right);}if (i == size - 1) {depth++;}}}return depth;}
559. N 叉树的最大深度
- 递归法
```java
public int maxDepth(Node root) {
if (root == null) {
} int depth = 0; for (int i = 0; i < root.children.size(); i++) {return 0;
} return depth + 1; }depth = Math.max(depth, maxDepth(root.children.get(i)));
class Node {
public int val;
public List
public Node() {}public Node(int _val) {val = _val;}public Node(int _val, List<Node> _children) {val = _val;children = _children;}
};
- 迭代法:层序遍历```javapublic int maxDepth(Node root) {if (root == null) {return 0;}Queue<Node> queue = new LinkedList<>();queue.offer(root);int depth = 0;while (!queue.isEmpty()) {int size = queue.size();for (int i = 0; i < size; i++) {Node node = queue.poll();for (Node child : node.children) {queue.offer(child);}if (i == size - 1) {depth++;}}}return depth;}
7. 二叉树的最小深度
和二叉树的最大深度一个套路?
递归法
直觉上好像和求最大深度差不多,其实还是差不少的。
遍历顺序上依然是后序遍历(因为要比较递归返回之后的结果),但在处理中间节点的逻辑上,最大深度很容易理解,最小深度可有一个误区,如图:
【来源:代码随想录】
这就重新审题了,题目中说的是:最小深度是从根节点到最近叶子节点的最短路径上的节点数量。,注意是叶子节点。
什么是叶子节点,左右孩子都为空的节点才是叶子节点!
// 递归法public int minDepth(TreeNode root) {if (root == null) {return 0;}int leftDepth = minDepth(root.left);int rightDepth = minDepth(root.right);if (root.left == null) {return rightDepth + 1;}if (root.right == null) {return leftDepth + 1;}// 左右节点都不为空return 1 + Math.min(leftDepth, rightDepth);}
迭代法
- 层序遍历
// 迭代:层序遍历public int minDepth2(TreeNode root) {int ans = 0;if (root == null) {return ans;}Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);int level = 0;while (!queue.isEmpty()) {int size = queue.size();level++;for (int i = 0; i < size; i++) {TreeNode node = queue.poll();if (node.left != null) {queue.offer(node.left);}if (node.right != null) {queue.offer(node.right);}if (node.left == null && node.right == null) {ans = level;return ans;}}}return ans;}
8. 完全二叉树的节点个数
// 法一: 递归public static int countNodes(TreeNode head) {if (head == null) {return 0;}int leftNum = countNodes(head.left);int rightNum = countNodes(head.right);return 1 + leftNum + rightNum;}
// 法二: 层序遍历public static int countNodes2(TreeNode head) {if (head == null) {return 0;}Queue<TreeNode> queue = new LinkedList<>();queue.offer(head);int counts = 0;while (!queue.isEmpty()) {TreeNode node = queue.poll();counts++;if (node.left != null) {queue.offer(node.left);}if (node.right != null) {queue.offer(node.right);}}return counts;}
9. 平衡二叉树
递归
此时大家应该明白了既然要求比较高度,必然是要后序遍历。
递归三步曲分析:
- 明确递归函数的参数和返回值
参数:当前传入节点。
返回值:以当前传入节点为根节点的树的高度。
那么如何标记左右子树是否差值大于1呢?
如果当前传入节点为根节点的二叉树已经不是二叉平衡树了,还返回高度的话就没有意义了。
所以如果已经不是二叉平衡树了,可以返回-1 来标记已经不符合平衡树的规则了。
public boolean isBalanced(TreeNode root) {return getHeight(root) == -1 ? false : true;}public int getHeight(TreeNode node) {if (node == null) {return 0;}int leftHeight = getHeight(node.left);if (leftHeight == -1) return -1;int rightHeight = getHeight(node.right);if (rightHeight == -1) return -1;if (Math.abs(leftHeight - rightHeight) > 1) {return -1;}return 1 + Math.max(leftHeight, rightHeight);}
10. 二叉树的所有路径
递归 + 回溯
这道题目要求从根节点到叶子的路径,所以需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。
在这道题目中将第一次涉及到回溯,因为我们要把路径记录下来,需要回溯来回退一一个路径在进入另一个路径。
前序遍历以及回溯的过程如图:
【来源代码随想录】
public List<String> ans = new ArrayList<>();public List<String> binaryTreePaths(TreeNode root) {List<Integer> paths = new ArrayList<>();process(root, paths);return ans;}//前序遍历 + 回溯private void process(TreeNode node, List<Integer> paths) {paths.add(node.val); // 中if (node.left == null && node.right == null) { // 叶子节点StringBuilder sb = new StringBuilder();for (int i = 0; i < paths.size() - 1; i++) {sb.append(paths.get(i)).append("->");}sb.append(paths.get(paths.size() - 1));ans.add(sb.toString());return;}if (node.left != null) {process(node.left, paths);paths.remove(paths.size() - 1);// 回溯}if (node.right != null) {process(node.right, paths);paths.remove(paths.size() - 1);// 回溯}}
迭代
至于非递归的方式,我们可以依然可以使用前序遍历的迭代方式来模拟遍历路径的过程
//法二: 迭代public List<String> binaryTreePaths2(TreeNode root) {List<String> result = new ArrayList<>();if (root == null) {return result;}Stack<Object> stack = new Stack<>();// 节点和路径同时入栈stack.push(root);stack.push(root.val + "");while (!stack.isEmpty()) {// 节点和路径同时出栈String path = (String) stack.pop();TreeNode node = (TreeNode) stack.pop();// 若找到叶子节点if (node.left == null && node.right == null) {result.add(path);}//右子节点不为空if (node.right != null) {stack.push(node.right);stack.push(path + "->" + node.right.val);}//左子节点不为空if (node.left != null) {stack.push(node.left);stack.push(path + "->" + node.left.val);}}return result;}
11.左叶子节点之和
递归法(后序)
public int sumOfLeftLeaves(TreeNode root) {if (root.left == null && root.right == null) {return 0;}return process(root, null);}private int process(TreeNode node, TreeNode parent) {if (node.left == null && node.right == null) {if (node == parent.left) {return node.val;} else {return 0;}}int leftSum = 0;if (node.left != null) {leftSum = process(node.left, node);}int rightSum = 0;if (node.right != null) {rightSum = process(node.right, node);}return leftSum + rightSum;}
迭代
本题迭代法使用前中后序都是可以的,只要把左叶子节点统计出来,就可以了
//迭代法:前序遍历public int sumOfLeftLeaves2(TreeNode root) {int sum = 0;if (root.left == null && root.right == null) {return sum;}Stack<TreeNode> stack = new Stack<>();stack.push(root);while (!stack.isEmpty()) {TreeNode node = stack.pop();if (node.left != null && node.left.left == null && node.left.right == null) {sum += node.left.val;}if (node.right != null) {stack.push(node.right);}if (node.left != null) {stack.push(node.left);}}return sum;}
12. 找树左下角的值
public static int findBottomLeftValue(TreeNode root) {if (root == null) {return 0;}Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);while (!queue.isEmpty()) {int size = queue.size();TreeNode head = null;for (int i = 0; i < size; i++) {TreeNode node = queue.poll();if (i == 0) {head = node;}if (node.left != null) {queue.offer(node.left);}if (node.right != null) {queue.offer(node.right);}if (i == (size - 1) && queue.isEmpty()) {return head.val;}}}return 0;}
13. 路径总和
-
递归法
public boolean hasPathSum(TreeNode root, int targetSum) {if (root == null) {return false;}targetSum -= root.val;// 叶子节点if (root.left == null && root.right == null) {return targetSum == 0;}if (root.left != null) {boolean left = hasPathSum(root.left, targetSum);if (left) {return true;}}if (root.right != null) {boolean right = hasPathSum(root.right, targetSum);if (right) {return true;}}return false;}
迭代法
// 法二:迭代public boolean hasPathSum2(TreeNode root, int targetSum) {if (root == null) return false;Stack<Object> stack = new Stack<>();stack.push(root);stack.push(root.val);while (!stack.isEmpty()) {int sum = (int) stack.pop();TreeNode node = (TreeNode) stack.pop();if (node.left == null && node.right == null && sum == targetSum) {return true;}if (node.right != null) {stack.push(node.right);stack.push(sum + node.right.val);}if (node.left != null) {stack.push(node.left);stack.push(sum + node.left.val);}}return false;}
14. 路径总和Ⅱ
private List<List<Integer>> res = new ArrayList<>();public List<List<Integer>> pathSum(TreeNode root, int targetSum) {if (root == null) {return res;}List<Integer> path = new ArrayList<>();path.add(root.val);process(root, targetSum - root.val, path);return res;}private void process(TreeNode node, int targetSum, List<Integer> path) {if (node.left == null && node.right == null && targetSum == 0) {List<Integer> list = new ArrayList<>();for (int i : path) {list.add(i);}res.add(list);return;}if (node.left != null) {path.add(node.left.val);targetSum -= node.left.val;process(node.left, targetSum, path);path.remove(path.size() - 1); //回溯targetSum += node.left.val; //回溯}if (node.right != null) {path.add(node.right.val);targetSum -= node.right.val;process(node.right, targetSum, path);path.remove(path.size() - 1); //回溯targetSum += node.right.val; //回溯}}
15.根据遍历结果构造二叉树
106.⭐从中序与后序遍历序列构造二叉树

public TreeNode buildTree(int[] inorder, int[] postorder) {return process(inorder, 0, inorder.length, postorder, 0, postorder.length);}// 区间不变量:左闭右开private TreeNode process(int[] inorder, int L1, int R1, int[] postorder, int L2, int R2) {// 一个元素都没有if (R1 - L1 < 1) {return null;}// 只有一个元素了if (R1 - L1 == 1) {return new TreeNode(inorder[L1]);}// 后序数组postorder里最后一个即为根结点int rootVal = postorder[R2 - 1];TreeNode root = new TreeNode(rootVal);int rootIndex = 0;// 根据根结点的值找到该值在中序数组inorder里的位置for ( rootIndex = L1; rootIndex < R1; rootIndex++) {if (inorder[rootIndex] == rootVal) {break;}}// 根据rootIndex划分左右子树root.left = process(inorder, L1, rootIndex, postorder, L2, L2 + (rootIndex - L1));root.right = process(inorder, rootIndex + 1, R1, postorder, L2 + (rootIndex - L1), R2 - 1);return root;}
105.⭐从前序与中序遍历序列构造二叉树

public TreeNode buildTree(int[] preorder, int[] inorder) {return process(preorder, 0, preorder.length, inorder, 0, inorder.length);}//不变量原则:左闭右开private TreeNode process(int[] preorder, int L1, int R1, int[] inorder, int L2, int R2) {if (R1 - L1 < 1) {return null;}if (R1 - L1 == 1) {return new TreeNode(preorder[L1]);}int rootVal = preorder[L1];TreeNode root = new TreeNode(rootVal);int rootIndex = 0;for (rootIndex = L2; rootIndex < R2; rootIndex++) {if (inorder[rootIndex] == rootVal) {break;}}root.left = process(preorder, L1 + 1, L1 + 1 + (rootIndex - L2), inorder, L2, rootIndex);root.right = process(preorder, L1 + 1+ (rootIndex - L2), R1, inorder, rootIndex + 1, R2);return root;}
思考题
前序和中序可以唯一确定一棵二叉树。
后序和中序可以唯一确定一棵二叉树。
那么前序和后序可不可以唯一确定一棵二叉树呢?
前序和后序不能唯一确定一棵二叉树!,因为没有中序遍历无法确定左右部分,也就是无法分割。
16. 最大二叉树
public TreeNode constructMaximumBinaryTree(int[] nums) {return process(nums, 0, nums.length);}// 不变量原则: 左闭右开private TreeNode process(int[] nums, int L, int R) {if (R - L< 1) {return null;}if (R - L == 1) {return new TreeNode(nums[L]);}int max = Integer.MIN_VALUE;int maxIndex = 0;for (int i = L; i < R; i++) {if (nums[i] > max) {max = nums[i];maxIndex = i;}}TreeNode root = new TreeNode(max);root.left = process(nums, L, maxIndex);root.right = process(nums, maxIndex + 1, R);return root;}
17.合并二叉树
递归
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {if (root1 == null) {// 如果root1为空,合并之后就应该是root2return root2;}if (root2 == null) {// 如果root2为空,合并之后就应该是root1return root1;}TreeNode root = new TreeNode(root1.val + root2.val);root.left = mergeTrees(root1.left, root2.left);root.right = mergeTrees(root1.right, root2.right);return root;}
使用队列
// 使用队列迭代public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {if (root1 == null) return root2;if (root2 ==null) return root1;Queue<TreeNode> queue = new LinkedList<>();queue.offer(root1);queue.offer(root2);while (!queue.isEmpty()) {TreeNode node1 = queue.poll();TreeNode node2 = queue.poll();// 此时两个节点一定不为空,val相加node1.val = node1.val + node2.val;// 如果两棵树左节点都不为空,加入队列if (node1.left != null && node2.left != null) {queue.offer(node1.left);queue.offer(node2.left);}// 如果两棵树右节点都不为空,加入队列if (node1.right != null && node2.right != null) {queue.offer(node1.right);queue.offer(node2.right);}// 若node1的左节点为空,直接赋值if (node1.left == null && node2.left != null) {node1.left = node2.left;}// 若node2的右节点为空,直接赋值if (node1.right == null && node2.right != null) {node1.right = node2.right;}}return root1;}
18.二叉搜索树中的搜索
法一:递归
public TreeNode searchBST(TreeNode root, int val) {if (root == null) {return root;}if (root.val == val) {return root;}if (root.val < val) {return searchBST(root.right, val);} else if (root.val > val) {return searchBST(root.left, val);}return null;}
法二:非递归
一提到二叉树遍历的迭代法,可能立刻想起使用栈来模拟深度遍历,使用队列来模拟广度遍历。
对于二叉搜索树可就不一样了,因为二叉搜索树的特殊性,也就是节点的有序性,可以不使用辅助栈或者队列就可以写出迭代法。
对于一般二叉树,递归过程中还有回溯的过程,例如走一个左方向的分支走到头了,那么要调头,在走右分支。
而对于二叉搜索树,不需要回溯的过程,因为节点的有序性就帮我们确定了搜索的方向。
例如要搜索元素为3的节点,我们不需要搜索其他节点,也不需要做回溯,查找的路径已经规划好了。
//法二:迭代public TreeNode searchBST2(TreeNode root, int val) {while (root != null) {if (root.val > val) {root = root.left;} else if (root.val < val) {root = root.right;} else {return root;}}return null;}
19.验证二叉搜索树
- 思路:中序遍历下,输出的二叉搜索树节点的数值是有序序列。有了这个特性,验证二叉搜索树,就相当于变成了判断一个序列是不是递增的了
递归法
可以递归中序遍历将二叉搜索树转变成一个数组,然后只要比较一下,这个数组是否是有序的,注意二叉搜索树中不能有重复元素, 把二叉树转变为数组来判断,是最直观的,但其实不用转变成数组,可以在递归遍历的过程中直接判断是否有序。
这道题目比较容易陷入两个陷阱:
陷阱1:不能单纯的比较左节点小于中间节点,右节点大于中间节点就完事了。
我们要比较的是 左子树所有节点小于中间节点,右子树所有节点大于中间节点

- 节点10大于左节点5,小于右节点15,但右子树里出现了一个6 这就不符合了! ```java //前一个节点 private TreeNode pre = null;
public boolean isValidBST(TreeNode root){ if (root == null) {
return true;
}
boolean left = isValidBST(root.left); // 左
//注意重复值 if (pre!=null && pre.val >= root.val) return false;
pre = root; // 记录前一个节点
boolean right = isValidBST(root.right); // 右 return left && right; }
<a name="JTA2a"></a>## 迭代法- 对中序遍历的迭代法稍加改动即可```java//法二:迭代public boolean isValidBST2(TreeNode root){if (root == null) {return true;}Stack<TreeNode> stack = new Stack<>();TreeNode cur = root;TreeNode pre = null;while (cur != null || !stack.isEmpty()) {if (cur != null) {stack.push(cur);cur = cur.left;} else {cur = stack.pop();if (pre != null && pre.val >= cur.val) return false;pre = cur;cur = cur.right;}}return true;}
20.二叉搜索树的最小绝对差
- 利用中序遍历是升序的特性,记录上一个遍历的节点
递归法
private TreeNode pre = null; // 记录上一个遍历的节点private int min = Integer.MAX_VALUE;public int getMinimumDifference(TreeNode root) {process(root);return min;}private void process(TreeNode root) {if (root == null) return;process(root.left);if (pre != null) {min = Math.min(min, root.val - pre.val);}pre = root;process(root.right);}
改迭代法
// 法二:迭代public int getMinimumDifference2(TreeNode root) {Stack<TreeNode> stack = new Stack<>();TreeNode cur = root;TreeNode pre = null; // 记录上一个遍历的节点int min = Integer.MAX_VALUE;while (cur != null || !stack.isEmpty()) {if (cur != null) {stack.push(cur);cur = cur.left;} else {cur = stack.pop();if (pre != null) {min = Math.min(min, cur.val - pre.val);}pre = cur;cur = cur.right;}}return min;}
21.二叉搜索树中的众数
- 如果不是二叉搜索树,最直观的方法一定是把这个树都遍历了,用map统计频率,取前面高频的元素的集合。
至于用前中后序那种遍历也不重要,因为就是要全遍历一遍,怎么个遍历法都行,层序遍历都没毛病!
这里采用前序遍历
private HashMap<Integer, Integer> map = new HashMap<>();public int[] findMode(TreeNode root) {if (root == null) {return new int[0];}process(root);List<Integer> list = new ArrayList<>();int maxCount = Integer.MIN_VALUE;for (Integer value : map.values()) {maxCount = Math.max(maxCount, value);}for (Integer key : map.keySet()) {Integer i = map.get(key);if ( i == maxCount) {list.add(key);}}int[] ans = new int[list.size()];int index = 0;for (int o : list) {ans[index++] = o;}return ans;}private void process(TreeNode node) {if (node == null) {return;}process(node.left);map.put(node.val, map.getOrDefault(node.val, 0) + 1);process(node.right);}
法二:利用二叉搜索树中序遍历的特性,
ArrayList<Integer> resList;int maxCount;int count;TreeNode pre;public int[] findMode(TreeNode root) {resList = new ArrayList<>();maxCount = 0;count = 0;pre = null;findMode1(root);int[] res = new int[resList.size()];for (int i = 0; i < resList.size(); i++) {res[i] = resList.get(i);}return res;}public void findMode1(TreeNode root) {if (root == null) {return;}findMode1(root.left);int rootValue = root.val;// 计数if (pre == null || rootValue != pre.val) {count = 1;} else {count++;}// 更新结果以及maxCountif (count > maxCount) {resList.clear();resList.add(rootValue);maxCount = count;} else if (count == maxCount) {resList.add(rootValue);}pre = root;findMode1(root.right);}
22.二叉树的最近公共祖先⭐
- 二叉树的递归套路
https://www.yuque.com/xcc/cqqo4i/ssqeng
public class Info {// 最近交汇的点public TreeNode ans;public boolean findO1;public boolean findO2;public Info(TreeNode ans, boolean findO1, boolean findO2) {this.ans = ans;this.findO1 = findO1;this.findO2 = findO2;}}public TreeNode lowestCommonAncestor(TreeNode root, TreeNode o1, TreeNode o2) {return process(root, o1, o2).ans;}private Info process(TreeNode node, TreeNode o1, TreeNode o2) {if (node == null) {return new Info(null, false, false);}Info leftInfo = process(node.left, o1, o2);Info rightInfo = process(node.right, o1, o2);boolean findO1 = node == o1 || leftInfo.findO1 || rightInfo.findO1;boolean findO2 = node == o2 || leftInfo.findO2 || rightInfo.findO2;TreeNode ans = null;if (leftInfo.ans != null) {ans = leftInfo.ans;}if (rightInfo.ans != null) {ans = rightInfo.ans;}if (ans == null) {if (findO1 && findO2) {ans = node;}}return new Info(ans, findO1, findO2);}
23.二叉搜索树的最近公共祖先
这道题没有普通二叉树那题的做法麻烦,因为可以利用二叉搜索树的有序的特性 ```java public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null) {return null;}if (root.val > p.val && root.val > q.val) {return lowestCommonAncestor(root.left, p, q);}if (root.val < p.val && root.val < q.val) {return lowestCommonAncestor(root.right, p, q);}return root;
}
<a name="M8DFM"></a># [701.二叉搜索树中的插入操作](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/)```javapublic TreeNode insertIntoBST(TreeNode root, int val) {TreeNode insert = new TreeNode(val);if (root == null) {return insert;}TreeNode cur = root;while (cur != null) {if (val < cur.val && cur.left != null) {cur = cur.left;}if (val < cur.val && cur.left == null) {cur.left = insert;break;}if (val > cur.val && cur.right != null) {cur = cur.right;}if (val > cur.val && cur.right == null) {cur.right = insert;break;}}return root;}
450.删除二叉搜索树中的节点⭐
- 第一种情况:没找到删除的节点
- 找到删除的节点
- 第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
- 第三种情况:删除节点的左孩子为空,右孩子不为空,删除节点,右孩子补位,返回右孩子为根节点
- 第四种情况:删除节点的右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点
- 第五种情况:左右孩子节点都不为空,则将删除节点的左子树头结点(左孩子)放到删除节点的右子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点。
第五种情况有点难以理解,看下面动画:
动画中棵二叉搜索树中,删除元素7, 那么删除节点(元素7)的左孩子就是5,删除节点(元素7)的右子树的最左面节点是元素8。
将删除节点(元素7)的左孩子放到删除节点(元素7)的右子树的最左面节点(元素8)的左孩子上,就是把5为根节点的子树移到了8的左孩子的位置。
迭代写法 ```java public TreeNode deleteNode(TreeNode root, int key) {
if (root == null) {return root;}TreeNode pre = null;TreeNode cur = root;while (cur != null) {if (key == cur.val) {break;}pre = cur;if (key < cur.val) {cur = cur.left;}else {cur = cur.right;}}//要删除的就是根节点if (pre == null) {return deleteOneNode(cur);}
if (pre.left != null && pre.left.val == key) {pre.left = deleteOneNode(cur);}if (pre.right != null && pre.right.val == key) {pre.right = deleteOneNode(cur);}return root;}// 删除节点逻辑, 返回新的根节点private TreeNode deleteOneNode(TreeNode cur) {if (cur.right == null) { //删除叶子节点 或 左子不为空,右子为空的节点return cur.left;} else if (cur.left == null){ // 删除左子为空,右子不为空的节点return cur.right;}//删除度为2的节点TreeNode node = cur.right;while (node.left != null) {node = node.left;}node.left = cur.left;return cur.right;}
- 递归写法```java//递归写法, 返回删除后整树的新头节点public TreeNode deleteNode2(TreeNode root, int key) {if (root == null) {return root;}if (root.val == key) {// 叶子节点if (root.left == null && root.right == null) {root = null;return root;} else if (root.left == null) { //度为1的节点root = root.right;return root;} else if (root.right == null) { //度为1的节点root = root.left;return root;} else {//度为2的节点TreeNode node = root.right;while (node.left != null) {node = node.left;}node.left = root.left;root = root.right;return root;}}if (key < root.val) {root.left = deleteNode2(root.left, key);}if (key > root.val) {root.right = deleteNode(root.right, key);}return root;}
669.修剪二叉搜索树⭐
递归法
来源【代码随想录】
[1, 3]区间在二叉搜索树的中可不是单纯的节点3和左孩子节点0就决定的,还要考虑节点0的右子树。
我们在重新关注一下第二个示例,如图:
所以以上的代码是不可行的!
从图中可以看出需要重构二叉树,想想是不是本题就有点复杂了。
其实不用重构那么复杂。
在上图中我们发现节点0并不符合区间要求,那么将节点0的右孩子 节点2 直接赋给 节点3的左孩子就可以了(就是把节点0从二叉树中移除),如图:
//法一:递归//传入一个根节点,返回修剪后的新的整树根节点public TreeNode trimBST(TreeNode root, int low, int high) {if (root == null) {return root;}if (root.val < low) {return trimBST(root.right, low, high);}if (root.val > high) {return trimBST(root.left, low, high);}root.left = trimBST(root.left, low, high);root.right = trimBST(root.right, low, high);return root;}
迭代法
因为二叉搜索树的有序性,不需要使用栈模拟递归的过程。
在剪枝的时候,可以分为三步:
- 将root移动到[L, R] 范围内,注意是左闭右闭区间
- 剪枝左子树
剪枝右子树
//法二:迭代public TreeNode trimBST2(TreeNode root, int low, int high) {if (root == null) return null;//处理头节点, 让root移动到[L, R]范围内while (root != null && (root.val < low || root.val > high)) {if (root.val < low) {root = root.right;} else {root = root.left;}}TreeNode cur = root;// 此时root已经在[L, R] 范围内,处理左孩子元素小于low的情况while (cur != null) {while (cur.left != null && cur.left.val < low) {cur.left = cur.left.right;}cur = cur.left;}cur = root;// 此时root已经在[L, R] 范围内,处理右孩子大于high的情况while (cur != null) {while (cur.right != null && cur.right.val > high) {cur.right = cur.right.left;}cur = cur.right;}return root;}
108.将有序数组转换为二叉搜索树
本质就是寻找分割点,分割点作为当前节点,然后递归左区间和右区间。
分割点就是数组中间位置的节点。
那么为问题来了,如果数组长度为偶数,中间节点有两个,取哪一个?
取哪一个都可以,只不过构成了不同的平衡二叉搜索树。
首先取数组中间元素的位置,不难写出int mid = (left + right) / 2;,这么写其实有一个问题,就是数值越界,例如left和right都是最大int,这么操作就越界了,在二分法中尤其需要注意!
public TreeNode sortedArrayToBST(int[] nums) {return process(nums, 0, nums.length - 1);}//左闭右开private TreeNode process(int[] nums, int L, int R) {if (L > R) {return null;}// int mid = (L + R) >> 1; // 这样写存在越界问题int mid = L + (R - L) >> 1;TreeNode node = new TreeNode(nums[mid]);node.left = process(nums, L, mid - 1);node.right = process(nums, mid + 1, R);return node;}
538.把二叉搜索树转换为累加树⭐
- 二叉搜索树 —-> 有序
- 那么有序的元素如果求累加呢?
其实这就是一棵树,大家可能看起来有点别扭,换一个角度来看,这就是一个有序数组[2, 5, 13],求从后到前的累加数组,也就是[20, 18, 13],是不是感觉这就简单了。
- 从树中可以看出累加的顺序是右中左,所以我们需要反中序遍历这个二叉树,然后顺序累加就可以了。
- 一个变量记录前一个节点的数值 ```java public TreeNode convertBST(TreeNode root) { process(root); return root; }
int pre = 0; //记录前一个节点的数值
//反中序遍历: 按右中左顺序遍历,累加即可 private void process(TreeNode node) { if (node == null) { return; }
process(node.right); // 右node.val = node.val + pre; // 中pre = node.val;process(node.left); // 左
} ```
