二叉树的遍历方式:
众所周知,二叉树遍历可分为前序遍历、中序遍历、后续遍历以及层次遍历,前三个也称为深度优先遍历(dfs),使用递归的方式;最后一个也称为广度优先(bfs),使用队列的方式。
假如说有这么一棵二叉树树:
- 前序遍历
public void travel(TreeNode root) {
if (root == null) return;
System.out.print(root.val + " ");
travel(root.left);
travel(root.right);
}
扩展:前序遍历还可以通过非递归的方式,也就是栈,因为栈跟递归都具有回溯的特性。代码如下:
public void travelWithStack(TreeNode root) {
TreeNode treeNode = root;
Stack<TreeNode> stack = new Stack<>();
while (treeNode != null || !stack.empty()) {
while (treeNode != null) {
System.out.print(treeNode.val + " ");
stack.push(treeNode);
treeNode = treeNode.left;
}
// 左子树结束,弹出当前节点,从而访问右节点
TreeNode node = stack.pop();
treeNode = node.right;
}
}
还有一种更容易理解的方式,第一步让根节点进入栈内,然后出栈,此时判断右节点、左节点是否为空,不为空的话添加到栈内,重复此操作,直到栈为空为止。(注意:之所以让右节点先进栈、左节点后进栈,是因为栈是后进先出的,这样就能先访问左节点了)
public void travelWithStack(TreeNode root) {
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.empty()) {
TreeNode currentNode = stack.pop();
System.out.println(currentNode.val);
if (currentNode.right != null) {
stack.push(currentNode.right);
}
if(currentNode.left != null){
stack.push(currentNode.left);
}
}
}
前序遍历可以看作指针在树上游走过程中,每到一个节点,将其打印。看leetcode617. 合并二叉树:
给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。
你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。
示例 1:
输入:
Tree 1 Tree 2
1 2
/ \ / \
3 2 1 3
/ \ \
5 4 7
输出:
合并后的树:
3
/ \
4 5
/ \ \
5 4 7
思路:先合并根节点,然后再递归合并左右子树。前序遍历的应用
public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
if (t1 == null && t2 == null) return null;
TreeNode node = new TreeNode((t1 == null ? 0 : t1.val) + (t2 == null ? 0 : t2.val));
// 前序遍历
node.left = mergeTrees(t1 == null ? null : t1.left, t2 == null ? null : t2.left);
node.right = mergeTrees(t1 == null ? null : t1.right, t2 == null ? null : t2.right);
return node;
}
- 中序遍历
public void travel(TreeNode root) {
if (root == null) return;
travel(root.left);
System.out.print(root.val + " ");
travel(root.right);
}
- 后序遍历
public void travel(TreeNode root) {
if (root == null) return;
travel(root.left);
travel(root.right);
System.out.print(root.val + " ");
}
给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
示例 :
1
/ \
2 3
/ \
4 5
返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。
思路:假如有一个递归方法,用来计算每一个节点的高度。对于任意一个节点来说,该节点的高度其实就是左孩子跟右孩子最大高度加1。
private int getHeight(TreeNode node) {
if (node == null) return 0;
int l = getHeight(node.left);
int r = getHeight(node.right);
return Math.max(l, r) + 1;
}
若此时,我传入一个根节点root,那么在求root的高度过程中,每个node的高度其实都被计算了。那么在这个过程中,来逐步更新最大直径即可。也就是两个两个节点之间的节点数-1。代码如下:
int ans = 0; // 节点数目
public int diameterOfBinaryTree(TreeNode root) {
getHeight(root);
return ans > 0 ? ans - 1 : ans;
}
/**
* 递归函数
* 用于求节点为node的高度
*/
private int getHeight(TreeNode node) {
if (node == null) return 0;
int l = getHeight(node.left);
int r = getHeight(node.right);
ans = Math.max(ans, l + r + 1);
return Math.max(l, r) + 1;
}
- 层次遍历
public void travel(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (queue.size() > 0) {
TreeNode currentNode = queue.poll();
System.out.print(currentNode.val + " ");
if (currentNode.left != null) {
queue.offer(currentNode.left);
}
if (currentNode.right != null) {
queue.offer(currentNode.right);
}
}
}
打印: 1 2 3 4 5 6 7
层次遍历使用的是队列来完成的,具体操作流程是:节点先进队列,接着节点出队列,然后判断该节点的左孩子跟右孩子是否为空,不为空的话继续添加到队列中。
- 扩展一下,如果让每一层的节点换行打印的话,需要如何操作? 代码如下:
public void travel(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (queue.size() > 0) {
int size = queue.size();
for (int i = 0; i < size; i++) {
TreeNode currentNode = queue.poll();
System.out.print(currentNode.val + " ");
if (currentNode.left != null) {
queue.offer(currentNode.left);
}
if (currentNode.right != null) {
queue.offer(currentNode.right);
}
}
System.out.println();
}
}
1
2 3
4 5 6 7
leetcode114. 二叉树展开为链表
给定一个二叉树,原地将它展开为一个单链表。
例如,给定二叉树:
1
/ \
2 5
/ \ \
3 4 6
将其展开为:
1
\
2
\
3
\
4
\
5
\
6
解题思路如下:
- 将左子树逐个遍历,比如上述的二叉树,考虑像将节点2的左子树移动到右边,变成2-3-4链表。
- 再将右子树逐个展开成链表,然后将左子树已经处理好的链表接到右侧去。
- 将左子树缓存下来之后,记得要将左子树设置为null
代码如下:
public void flatten(TreeNode root) {
travel(root);
}
/**
* 递归函数
* 将节点node左子树移动到右边
*/
private TreeNode travel(TreeNode node) {
if (node == null) return null;
TreeNode l = travel(node.left);
TreeNode r = travel(node.right);
if(l != null ){
node.right = l;
node.left = null;
// 要用当前的末端去链接 r
// 即l的末端
while ( l.right !=null){
l = l.right;
}
l.right = r;
}
return node;
}
leetcode236. 二叉树的最近公共祖先
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
示例 1:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出: 3
解释: 节点 5 和节点 1 的最近公共祖先是节点 3。
示例 2:
输入: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
输出: 5
解释: 节点 5 和节点 4 的最近公共祖先是节点 5。因为根据定义最近公共祖先节点可以为节点本身。
解题思路如下:
- 因为求最近的公共祖先,所以可以采用自底向上的方式,即后续遍历。找到的第一个公共祖先即为最近的。
- 假如说,最近的公共祖先找到了。那么分两种情况:
- 节点p q必定分布在公共祖先的两侧;
- p、q其中一个为公共祖先,另一个在公共祖先的左侧或者右侧;
思路有了,代码就清晰了,如下:
TreeNode resultNode;
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
checkIsContainOne(root, p, q);
return resultNode;
}
/**
* 判断节点node 是否包含 p 或者 q
* 自底向上,后续遍历
*/
private boolean checkIsContainOne(TreeNode node, TreeNode p, TreeNode q) {
if (node == null) return false;
boolean l = checkIsContainOne(node.left, p, q);
boolean r = checkIsContainOne(node.right, p, q);
if (r && l || (l && node.val == q.val || l && node.val == p.val) || (r && node.val == p.val || r && node.val == q.val))
resultNode = node;
// 当前节点node包含p或者q ,或者当前节点node的左孩子or右孩子包含p或者q,都向上返回
return node.val == p.val || node.val == q.val || (l || r);
}