递归遍历基础篇
前序遍历,中序遍历,后序遍历是根据处理根节点的位置来命名的。
树的处理大多用到了递归,递归需要知道终止条件。
前序遍历(中左右)
中左右,先处理根节点,再处理左子树,再处理右子树
java
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
preOrder(res, root);
return res;
}
private void preOrder(List<Integer> res, TreeNode root) {
if(root==null){
return;
}
res.add(root.val);
preOrder(res, root.left);
preOrder(res, root.right);
}
}
非递归版实现前序遍历
使用栈,当前节点处理完,先塞入右节点(后处理),再塞入左节点。
java
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
List<Integer> res = new ArrayList<>();
if (root == null) {
return res;
}
//右左
stack.add(root);
while (!stack.isEmpty()) {
TreeNode pop = stack.pop();
res.add(pop.val);
if (pop.right != null) {
stack.push(pop.right);
}
if (pop.left != null) {
stack.push(pop.left);
}
}
return res;
}
}
中序遍历(左中右)
递归方式
java
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
inOrder(res, root);
return res;
}
private void inOrder(List<Integer> res, TreeNode root) {
if (root == null) {
return;
}
inOrder(res, root.left);
res.add(root.val);
inOrder(res, root.right);
}
}
非递归,左中右,先找到最左节点,处理当前节点,处理右节点。把最左边的节点都压入栈中。
java
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null) {
return res;
}
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (cur != null || !stack.isEmpty()) {
if (cur != null) {
stack.push(cur);
cur = cur.left;
} else {
TreeNode pop = stack.pop();
res.add(pop.val);
cur = pop.right;
}
}
return res;
}
后序遍历(左右中)
递归方式
java
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
postOrder(res, root);
return res;
}
public void postOrder(List<Integer> res, TreeNode root) {
if (root == null) {
return;
}
postOrder(res,root.left);
postOrder(res,root.right);
res.add(root.val);
}
}
非递归方式,左右中的逆序是中右左,处理可以参考前序遍历,最后进行倒序。
java
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()) {
TreeNode pop = stack.pop();
res.add(pop.val);
if(pop.left!=null){
stack.push(pop.left);
}
if(pop.right!=null){
stack.push(pop.right);
}
}
Collections.reverse(res);
return res;
}
}
层序遍历
LeetCode199.二叉树的右视图
获取当前行的最后一个元素。
java
class Solution_LC199 {
public List<Integer> rightSideView(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null) {
return res;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
while (size > 0) {
size--;
TreeNode node = queue.poll();
if (size == 0) {
res.add(node.val);
}
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
}
return res;
}
}
LeetCode103.二叉树的锯齿形层序遍历
对树进行判空
获取当前行的元素,需要获取队列的大小。
java
class Solution {
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if (root == null) {
return res;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
int index = 0;
while (!queue.isEmpty()) {
index++;
LinkedList<Integer> list = new LinkedList<>();
int size = queue.size();
while (size > 0) {
size--;
TreeNode node = queue.poll();
if (index % 2 == 0) {
list.addFirst(node.val);
} else {
list.addLast(node.val);
}
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
}
res.add(list);
}
return res;
}
}
剑指 Offer II 044. 二叉树每层的最大值
原理同上。
java
class Solution_JZ044 {
public List<Integer> largestValues(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null) {
return res;
}
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
int max = Integer.MIN_VALUE;
while (size > 0) {
size--;
TreeNode node = queue.poll();
if (node.val > max) {
max = node.val;
}
if (node.left != null) {
queue.offer(node.left);
}
if (node.right != null) {
queue.offer(node.right);
}
}
res.add(max);
}
return res;
}
}
Leetcode101. 对称二叉树
递归
比较最左边和最右边的元素。
java
class Solution {
public boolean isSymmetric(TreeNode root) {
if (root == null) {
return true;
}
return dfs(root.left, root.right);
}
private boolean dfs(TreeNode left, TreeNode right) {
if (left == null && right == null) {
return true;
}
if (left == null || right == null) {
return false;
}
if (left.val != right.val) {
return false;
}
return dfs(left.left, right.right) && dfs(right.left, left.right);
}
}
使用队列
java
class Solution {
public boolean isSymmetric(TreeNode root) {
if (root == null || (root.left == null && root.right == null)) {
return true;
}
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;
}
if (left == null || right == null) {
return false;
}
if (left.val != right.val) {
return false;
}
queue.offer(left.left);
queue.offer(right.right);
queue.offer(right.left);
queue.offer(left.right);
}
return true;
}
}
前序遍历
LeetCode257.二叉树的所有路径
结束条件:该节点是叶子节点,结果集添加
path
如何添加连接字符
->
。
java
class Solution {
List<String> res = new ArrayList<>();
public List<String> binaryTreePaths(TreeNode root) {
dfs(root, "");
return res;
}
private void dfs(TreeNode root, String s) {
if (root == null) {
return;
}
s = s + root.val;
if (root.left == null && root.right == null) {
res.add(s);
}else {
dfs(root.left, s + "->");
dfs(root.right, s + "->");
}
}
}
LeetCode129.求根到叶子节点数字之和
按照上题的思路来,先把结果添加到集合里面。
java
class Solution {
List<Integer> res = new ArrayList<>();
public int sumNumbers(TreeNode root) {
if (root == null) {
return 0;
}
dfs(root, 0);
int sum = 0;
for (int i = 0; i < res.size(); i++) {
sum += res.get(i);
}
return sum;
}
private void dfs(TreeNode root, int num) {
if (root == null) {
return;
}
num = num * 10 + root.val;
if (root.left == null && root.right == null) {
res.add(num);
} else {
dfs(root.left, num);
dfs(root.right, num);
}
}
}
递归,当前节点的结果等于左子树操作+右子树操作
java
class Solution {
List<Integer> res = new ArrayList<>();
public int sumNumbers(TreeNode root) {
if (root == null) {
return 0;
}
return dfs2(root, 0);
}
private int dfs2(TreeNode root, int pre) {
if (root == null) {
return 0;
}
int sum = pre * 10 + root.val;
if (root.left == null && root.right == null) {
return sum;
}
return dfs2(root.right, sum) + dfs2(root.left, sum);
}
}
LeetCode112.路径总和
不断累加值,判断是否和目标值相等
java
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
return dfs(root, targetSum, 0);
}
private boolean dfs(TreeNode root, int targetSum, int pre) {
if (root == null) {
return false;
}
if (root.left == null && root.right == null) {
return pre + root.val == targetSum;
} else {
return dfs(root.left, targetSum, pre + root.val) || dfs(root.right, targetSum, pre + root.val);
}
}
}
更新目标值
java
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
if (root == null) {
return false;
}
if (root.left == null && root.right == null) {
return targetSum == root.val;
}
return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val);
}
}
LeetCode113.路径总和II
用队列记录当前路径下的元素,方便回退。
java
class Solution {
List<List<Integer>> res = new LinkedList<>();
LinkedList<Integer> queue = new LinkedList<>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
dfs(root,targetSum );
return res;
}
private void dfs(TreeNode root, int targetSum) {
if (root == null) {
return;
}
targetSum -= root.val;
queue.offer(root.val);
if (root.left == null && root.right == null && targetSum == 0) {
res.add(new ArrayList<>(queue));
} else {
dfs(root.left, targetSum);
dfs(root.right, targetSum);
}
queue.pollLast();
}
}
LeetCode437.路径总和 III(**)
定义一个以某节点为开始节点获取路径总和为
targetSum
的方法。不一定是叶子节点
用long代替int,因为有测试用例减溢出。
java
class Solution {
public int pathSum(TreeNode root, int targetSum) {
if (root == null) {
return 0;
}
int res = rootSum(root, targetSum);
return res + pathSum(root.left, targetSum) + pathSum(root.right, targetSum);
}
private int rootSum(TreeNode root, long targetSum) {
int ret = 0;
if (root == null) {
return 0;
}
if (targetSum == root.val) {
ret++;
}
return ret + rootSum(root.left, targetSum - root.val) + rootSum(root.right, targetSum - root.val);
}
}
中序遍历
LeetCode98.验证二叉搜索树
左子树上的节点都小于根节点,右子树上的值都大于根节点。
递归。
java
class Solution {
public boolean isValidBST(TreeNode root) {
return isValidBST(root, Long.MAX_VALUE, Long.MIN_VALUE);
}
private boolean isValidBST(TreeNode root, long maxValue, long minValue) {
if (root == null) {
return true;
}
if (root.val >= maxValue || root.val <= minValue) {
return false;
}
return isValidBST(root.left, root.val, minValue) && isValidBST(root.right, maxValue, root.val);
}
}
中序遍历
中序遍历 ,先左再中后右,左<中<右
比较数组是否有序,用tmp来缓存最小元素。
java
class Solution {
public boolean isValidBST(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
long tmp = Long.MIN_VALUE;
TreeNode cur = root;
while (!stack.isEmpty() || cur != null) {
if (cur != null) {
stack.push(cur);
cur = cur.left;
}else {
cur = stack.pop();
if (cur.val <= tmp) {
return false;
}
tmp = cur.val;
cur = cur.right;
}
}
return true;
}
}
剑指 Offer 54. 二叉搜索树的第k大节点
第n大,就是数组中第n-k个元素。第2大的元素逆序在索引1的位置。
右中左,即中序遍历的数组的倒序。
java
class Solution_LCR174 {
int res;
int index = 0;
public int findTargetNode(TreeNode root, int cnt) {
dfs(root, cnt);
return res;
}
private void dfs(TreeNode root, int cnt) {
if (root == null) {
return;
}
dfs(root.right, cnt);
index++;
if (index == cnt) {
res = root.val;
}
dfs(root.left, cnt);
}
}
Leetcode230. 二叉搜索树中第K小的元素
同上,不需要倒序,更简单
java
class Solution {
int k;
int res;
int index = 0;
public int kthSmallest(TreeNode root, int k) {
this.k = k;
dfs(root);
return res;
}
private void dfs(TreeNode root) {
if (root == null) {
return;
}
dfs(root.left);
index++;
if (index == k) {
res = root.val;
}
dfs(root.right);
}
}
剑指offer36: 二叉搜索树与双向链表(*)
pre节点是前节点,获取最左边的节点为pre节点。不断更新pre节点。
核心还是中序遍历。
java
class Solution {
Node pre = null, head = null;
public Node treeToDoublyList(Node root) {
if (root == null) return root;
dfs(root);
head.left = pre;
pre.right = head;
return head;
}
void dfs(Node root) {
if (root == null) {
return;
}
dfs(root.left);
if (pre != null) {
pre.right = root;
} else {
head = root;
}
root.left = pre;
pre = root;
dfs(root.right);
}
}
Leetcode.538. 把二叉搜索树转换为累加树(*)
理解题意,原先是二叉搜索树,当先不一定是二叉搜索树。当前节点为原有树的大于等于当前节点(当前节点和所有右子树节点)的和。
java
class Solution {
int sum = 0;
public TreeNode convertBST(TreeNode root) {
if (root == null) {
return null;
}
convertBST(root.right);
sum += root.val;
root.val = sum;
convertBST(root.left);
return root;
}
}
后序遍历
Leetcode104. 二叉树的最大深度
方法一:递归(后续遍历,左右中)
方法二:层序遍历
java
class Solution {
public int maxDepth(TreeNode root) {
if (root == null) {
return 0;
} else {
return Math.max(maxDepth(root.right), maxDepth(root.left)) + 1;
}
}
}
LeetCode226. 翻转二叉树
要暂存左边的树
java
class Solution {
public TreeNode invertTree(TreeNode root) {
if (root == null) {
return null;
}
TreeNode left = invertTree(root.left);
TreeNode right = invertTree(root.right);
root.right = left;
root.left = right;
return root;
}
}
Leetcode110. 判断是否是平衡二叉树
求深度的扩展
java
class Solution {
public boolean isBalanced(TreeNode root) {
if (root == null) {
return true;
}
return Math.abs(getDepth(root.left) - getDepth(root.right)) <= 1 & isBalanced(root.left)
&& isBalanced(root.right);
}
private int getDepth(TreeNode root) {
if (root == null) {
return 0;
}
return 1 + Math.max(getDepth(root.right), getDepth(root.left));
}
}
LeetCode543.二叉树的直径
结果最大的数据不一定是根节点。
每一个节点的对应的直径等于左子树的深度+右子树的深度。
java
class Solution {
int ans;
public int diameterOfBinaryTree(TreeNode root) {
if (root == null) {
return 0;
}
depth(root);
return ans;
}
private int depth(TreeNode root) {
if (root == null) {
return 0;
}
int left = depth(root.left);
int right = depth(root.right);
ans = Math.max(ans, right + left);
return 1 + Math.max(left, right);
}
}
LeetCode124.二叉树中的最大路径和
计算以某节点为起点,最长的路径。路径和=左最大路径+右最大路径+当前节点的值。
如果该路径大于0,则选;否则不选。
java
class Solution {
int ans = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
maxGain(root);
return ans;
}
public int maxGain(TreeNode node) {
if (node == null) {
return 0;
}
int left = Math.max(maxGain(node.left), 0);
int right = Math.max(maxGain(node.right), 0);
ans = Math.max(ans, node.val + left + right);
return node.val + Math.max(left, right);
}
}
Leetcode236. 二叉树的最近公共祖先(**)
左子树能找到,两个节点都在左边;左右子树都找到,返回root;左右子树都找不到,返回
java
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || p == root || q == root) {
return root;
}
TreeNode leftNode = lowestCommonAncestor(root.left, p, q);
TreeNode rightNode = lowestCommonAncestor(root.right, p, q);
if (leftNode == null && rightNode == null) {
return null;
}
if (leftNode == null) {
return rightNode;
}
if (rightNode == null) {
return leftNode;
}
return root;
}
}
二叉搜索树
Leetcode235. 二叉搜索树的最近公共祖先
遍历树的元素
java
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
while (true) {
if (root.val > p.val && root.val > q.val) {
root = root.left;
} else if (root.val < p.val && root.val < q.val) {
root = root.right;
} else {
break;
}
}
return root;
}
}
剑指offer33: 验证二叉搜索树的后序遍历序列
后续遍历是左右中的结构,最后一个元素是root
序列中第一个元素大于root元素,该元素及以后是右子树的序列。
最后一个元素是根元素,不是右子树的一部分。
java
class Solution {
public boolean verifyTreeOrder(int[] postorder) {
// 左右中
return dfs(postorder, 0, postorder.length - 1);
}
private boolean dfs(int[] postorder, int left, int right) {
if (left >= right) {
return true;
}
int tmp = left;
int root = postorder[right];
while (tmp < right && postorder[tmp] < root) {
tmp++;
}
int mid = tmp;
while (tmp < right) {
if (postorder[tmp] < root) {
return false;
}
tmp++;
}
return dfs(postorder, left, mid - 1) && dfs(postorder, mid, right - 1);
}
}
二叉树的修改构造
LeetCode105.从前序和中序遍历构造二叉树
前序 中左右 中序 左中右
分割中序遍历的序列,获取到左子树的长度和右子树的长度。
对数组的操作,要么是拷贝数组,要么是指定索引。
java
class Solution {
Map<Integer, Integer> map = new HashMap<>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
for (int i = 0; i < inorder.length; i++) {
map.put(inorder[i], i);
}
return buildTree(preorder, 0, preorder.length - 1, inorder, 0, inorder.length - 1);
}
private TreeNode buildTree(int[] preorder, int preLeft, int preRight, int[] inorder, int inLeft, int inRight) {
if (preRight < preLeft || inRight < inLeft) {
return null;
}
int rootIndex = map.get(preorder[preLeft]);
TreeNode root = new TreeNode(preorder[preLeft]);
int leftLength = rootIndex - inLeft;
root.left = buildTree(preorder, preLeft + 1, preLeft + leftLength, inorder, inLeft, rootIndex);
root.right = buildTree(preorder, preLeft + leftLength + 1, preRight, inorder, rootIndex + 1, inRight);
return root;
}
}
Leetcode 297.二叉树的序列化与反序列化
使用先序遍历来构成数组,数组转换成树。
java
public class Codec {
public String serialize(TreeNode root) {
return rserialize(root, "");
}
private String rserialize(TreeNode root, String s) {
if (root == null) {
s += "NONE,";
} else {
s += root.val + ",";
s = rserialize(root.left, s);
s = rserialize(root.right, s);
}
return s;
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
String[] dataList = data.split(",");
return rdeserialize(new ArrayList(Arrays.asList(dataList)));
}
private TreeNode rdeserialize(List<String> dataList) {
if (dataList.get(0).equals("NONE")) {
dataList.remove(0);
return null;
}
TreeNode root = new TreeNode(Integer.valueOf(dataList.get(0)));
dataList.remove(0);
root.left = rdeserialize(dataList);
root.right = rdeserialize(dataList);
return root;
}
}
二叉树的递归思维
LeetCode572.另一个树的子树
简化成两棵树是否相等
java
class Solution {
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
if (root == null) {
return false;
}
return isSameTree(root, subRoot) || isSubtree(root.left, subRoot) || isSubtree(root.right, subRoot);
}
public boolean isSameTree(TreeNode s, TreeNode t) {
if (s == null && t == null) {
return true;
}
if (s == null || t == null || s.val != t.val) {
return false;
}
return isSameTree(s.left, t.left) && isSameTree(s.right, t.right);
}
}
其他
LeetCode114. 二叉树展开为链表
比较简单的方法就是先序遍历元素。
java
class Solution {
List<TreeNode> list = new ArrayList<>();
public void flatten(TreeNode root) {
dfs(root);
for (int i = 1; i < list.size(); i++) {
TreeNode prev = list.get(i - 1), cur = list.get(i);
prev.left = null;
prev.right = cur;
}
}
private void dfs(TreeNode root) {
if (root == null) {
return;
}
list.add(root);
dfs(root.left);
dfs(root.right);
}
}
把右节点放在左节点的最右节点的右边,把左节点放在右边,这样顺序是正确的。
java
class Solution {
public void flatten(TreeNode root) {
TreeNode cur = root;
while (cur != null) {
TreeNode left = cur.left;
if (left != null) {
TreeNode tmp = left;
while (tmp.right != null) {
tmp = tmp.right;
}
tmp.right = cur.right;
cur.left = null;
cur.right = left;
}
cur = cur.right;
}
}
}