上一篇学习了二叉树的遍历方式:前中后序遍历 和 层序遍历。本篇学习一下二叉树其他的一些特性。
翻转二叉树
相关题目:
此题有多种解法,要对二叉树进行翻转,就要首先对二叉树进行遍历,因此使用前一篇提到的几种遍历方式即可。
下面只放上使用 前序遍历 和 层序遍历 处理的代码,以及 中序遍历 的 迭代法 解法,因为 中序遍历 的 迭代法 有些特殊。 而 统一迭代法 的 中后序遍历 的代码与 前序遍历 的代码基本一致,因此这里就不放上来了。
前序遍历
递归法,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public TreeNode invertTree(TreeNode root) { recurse(root); return root; }
public void recurse(TreeNode node) { if (node == null) { return; } TreeNode tmpNode = node.left; node.left = node.right; node.right = tmpNode; recurse(node.left); recurse(node.right); }
|
栈(统一迭代法):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public TreeNode invertTree(TreeNode root) { if (root == null) { return root; } Stack<TreeNode> stack = new Stack<TreeNode>(); stack.push(root); while (!stack.empty()) { TreeNode node = stack.peek(); if (node != null) { stack.pop(); if (node.right != null) { stack.push(node.right); } if (node.left != null) { stack.push(node.left); } stack.push(node); stack.push(null); } else { stack.pop(); node = stack.pop(); TreeNode tmpNode = node.left; node.left = node.right; node.right = tmpNode; } } return root; }
|
中序遍历
中序遍历的递归法,常规的 中序遍历 最后遍历的是 右子节点,而这里在中间对左右节点进行了交换,因此最后仍然需要处理 左子节点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public TreeNode invertTree(TreeNode root) { recurse(root); return root; }
public void recurse(TreeNode node) { if (node == null) { return; } recurse(node.left); TreeNode tmpNode = node.left; node.left = node.right; node.right = tmpNode; recurse(node.left); }
|
层序遍历
层序遍历的 递归法 处理似乎不太适合这一题,因为最终解出来与 前序遍历 的递归法类似,因此直接使用 队列法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public TreeNode invertTree(TreeNode root) { if (root == null) { return root; } Queue<TreeNode> queue = new LinkedList<>(); queue.add(root); while (!queue.isEmpty()) { int len = queue.size(); while (len > 0) { TreeNode node = queue.poll(); if (node.left != null) { queue.add(node.left); } if (node.right != null) { queue.add(node.right);mark
} TreeNode tmp = node.left; node.left = node.right; node.right = tmp; len--; } } return root; }
|
对称二叉树
相关题目:
- 101.对称二叉树
- 100.相同的树
- 572.另一棵树的子树
以 [101.对称二叉树] 这题为例,本题对对称二叉树的定义是:根节点的左右子树是否轴对称。也就是说,我们要求的是 根结点的左右子树是否是互相翻转。
递归法
首先按照 递归法 来处理,遵循递归法的三步走逻辑:
- **确定参数和返回值。**要比较的是左右子树,因此参数是左节点和右节点;返回值是左节点和右节点是否相互翻转。
- **确定终止条件。**将左右节点不是相互翻转的条件列出来。
- 确定单层递归的逻辑。 单层递归的逻辑就是,在左右节点都不为空时,它们的外侧节点和内侧节点是否相同,如果相同则返回
true
。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public boolean isSymmetric(TreeNode root) { return reverse(root.left, root.right); }
public boolean reverse(TreeNode leftNode, TreeNode rightNode) { if (leftNode == null && rightNode != null) { return false; } else if (leftNode != null && rightNode == null) { return false; } else if (leftNode == null && rightNode == null) { return true; } else if (leftNode.val != rightNode.val) { return false; } boolean outside = reverse(leftNode.left, rightNode.right); boolean inside = reverse(leftNode.right, rightNode.left); return outside && inside; }
|
迭代法
首先使用 队列 来求解,虽然用了 队列,但是并不是 层序遍历,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public boolean isSymmetric(TreeNode root) { Queue<TreeNode> queue = new LinkedList<>(); queue.add(root.left); queue.add(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.add(left.left); queue.add(right.right); queue.add(left.right); queue.add(right.left); } return true; }
|
看下 [100.相同的树] 这题,递归法就不贴了,直接贴迭代法的处理,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public boolean isSameTree(TreeNode p, TreeNode q) { Queue<TreeNode> queue = new LinkedList<>(); queue.add(p); queue.add(q); 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.add(left.left); queue.add(right.left); queue.add(left.right); queue.add(right.right); } return true; }
|
再来看下 [572.另一棵树的子树] 这题,解法类似,主要是要确定好需要比较的范围,这次需要判断的情况有如下三种:
- B树是A树 从根节点开始的子树。
- B树是A树 左子树的子树。
- B树是A树 右子树的子树。
递归法的处理如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public boolean isSubtree(TreeNode root, TreeNode subRoot) { if (root == null || subRoot == null) { return false; } return reverse(root, subRoot) || isSubtree(root.left, subRoot) || isSubtree(root.right, subRoot); }
public boolean reverse(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) { return false; } boolean leftResult = reverse(left.left, right.left); boolean rightResult = reverse(left.right, right.right); return leftResult && rightResult; }
|
再来看下迭代法,使用了 层序遍历,相当于加了两层 for
循环:第一层for循环遍历A树,找到与B树跟节点一样值的节点,然后沿着该节点继续遍历,判断B树是否是子树。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| public boolean isSubtree(TreeNode root, TreeNode subRoot) { Queue<TreeNode> queue = new LinkedList<>(); queue.add(root); while (!queue.isEmpty()) { int len = queue.size(); while (len-- > 0) { TreeNode node = queue.poll(); if (node.val == subRoot.val) { boolean result = isSame(node, subRoot); if (result) { return result; } } if (node.left != null) { queue.add(node.left); } if (node.right != null) { queue.add(node.right); } } } return false; }
public boolean isSame(TreeNode root, TreeNode subRoot) { if (root == null && subRoot == null) { return true; } else if (root == null || subRoot == null) { return false; } Queue<TreeNode> queue = new LinkedList<>(); queue.add(root); queue.add(subRoot); 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.add(left.left); queue.add(right.left); queue.add(left.right); queue.add(right.right); } return true; }
|
完全二叉树的节点个数
相关题目:
普通解法(层序遍历)
先不用管题目中的 完全二叉树,将其按照普通的二叉树来处理,直接使用层序遍历即可,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public int countNodes(TreeNode root) { if (root == null) { return 0; } Queue<TreeNode> queue = new LinkedList<>(); queue.add(root); int num = 0; while (!queue.isEmpty()) { int len = queue.size(); num += len; while (len-- > 0) { TreeNode node = queue.poll(); if (node.left != null) { queue.add(node.left); } if (node.right != null) { queue.add(node.right); } } } return num; }
|
利用完全二叉树的特性(递归)
再来结合题目中给出 完全二叉树 的条件来解答,对于一个完全二叉树,它有两种情况:
- 它是一个满二叉树
- 它的最后一层叶子节点没有填满
对于 满二叉树,我们可以利用公式 [2 ^ depth - 1
] 来计算它的节点数量,因此利用这个特性,我们可以写出下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public int countNodes(TreeNode root) { if (root == null) { return 0; } TreeNode left = root.left; TreeNode right = root.right; int leftDepth = 0, rightDepth = 0; while (left != null) { left = left.left; leftDepth++; } while (right != null) { right = right.right; rightDepth++; } if (leftDepth == rightDepth) { return (2 << leftDepth) - 1; } return countNodes(root.left) + countNodes(root.right) + 1; }
|
平衡二叉树
相关题目:
在解答本题前,需要首先需要了解二叉树的 深度 和 高度 的概念:二叉树的深度是从根节点开始递增的,根节点的深度是1;高度是从根节点开始递减的,最后的叶子节点的高度是1。
因此我们求深度时,可以从上到下去查,因此需要用到 前序遍历 (使用 层序遍历 也可以);而求高度时,需要从下到上去查,因此需要用到 后序遍历。这里再补充一点,如果要求的是二叉树的 最大深度,那么可以转换成求二叉树的高度,因为根节点的高度就是二叉树的最大深度。
回到题目上来,本题的解题思路是求各个节点的树的高度,然后比较左右子树的高度差值是否大于1,如果大于1,那么就返回 false
。要求的是二叉树的高度,因此需要用到 后序遍历。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| 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; } else { return 1 + Math.max(leftHeight, rightHeight); } }
|
看完这题,我们再回过头看下 [104.二叉树的最大深度]。现在我们知道求二叉树的深度需要用到 前序遍历,因为本题是求 最大深度,所以可以转换为求二叉树的高度,所以也可以使用 后序遍历 或 层序遍历 来处理。之前使用的是 层序遍历 来处理的,现在尝试使用 前序遍历 来处理一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| int maxDepth = 0;
public int maxDepth(TreeNode root) { if (root != null) { getDepth(root, 1); } return maxDepth; }
public void getDepth(TreeNode node, int depth) { maxDepth = maxDepth < depth ? depth : maxDepth; if (node.left != null) { getDepth(node.left, depth + 1); } if (node.right != null) { getDepth(node.right, depth + 1); } }
|
二叉树的所有路径(回溯算法)
相关题目:
此题引入了一种新的算法:回溯算法。回溯算法是在递归的基础上做的一些优化,本题中要返回二叉树根节点到叶子结点的所有路径,因此要用到 前序遍历,在前序遍历的基础上进行回溯处理,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| public List<String> binaryTreePaths(TreeNode root) { List<String> resList = new ArrayList<>(); if (root != null) { List<Integer> path = new ArrayList<>(); getNode(root, path, resList); } return resList; }
public void getNode(TreeNode node, List<Integer> path, List<String> resList) { path.add(node.val); if (node.left == null && node.right == null) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < path.size(); i++) { sb.append(path.get(i)); if (i != path.size() - 1) { sb.append("->"); } } resList.add(sb.toString()); } if (node.left != null) { getNode(node.left, path, resList); path.remove(path.size() - 1); } if (node.right != null) { getNode(node.right, path, resList); path.remove(path.size() - 1); } }
|
将上面的递归法改造为迭代法,处理如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public List<String> binaryTreePaths(TreeNode root) { List<String> resList = new ArrayList<>(); if (root != null) { Stack<Object> stack = new Stack<>(); stack.push(root); stack.push(root.val + ""); while (!stack.empty()) { String path = stack.pop().toString(); TreeNode node = (TreeNode) stack.pop(); if (node.left == null && node.right == null) { resList.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 resList; }
|