持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第1天,二叉树展开为链表[树处理的核心--如何处理任意一颗子树] - 掘金 (juejin.cn))
前言
对于树操作而言,遍历是基础。对于各种处理树问题,如果能懂如何处理好任意一棵子树,那么这棵树就被处理好了。通过二叉树展开为链表来练习所有处理树问题的核心:如何处理好任意一颗子树(只有root,只有左孩子,只有右孩子,两孩子都有)
一、二叉树展开为链表
二、处理树
1、暴力解决
把树节点按照前序遍历的前驱后继关系存入队列中,然后依次修改每个节点的left/right指针即可。
// 二叉树展开为链表。
public class Flatten {
/*
target:将二叉树展开为单向链表,即left=null,right指向后继。
后继的定义为前序遍历的后继。
直观方法:前序遍历把每个节点串起来。
*/
public void flatten(TreeNode root) {
preOrder(root);
// 收集到节点队列,顺序改变其left,right指针。
TreeNode head = que.poll();
if (head != null) head.left = null;
while (!que.isEmpty()) {
TreeNode cur = que.poll();
head.right = cur;
cur.left = null;
head = cur;
}
}
Queue<TreeNode> que = new ArrayDeque<>();
private void preOrder(TreeNode root) {
if (root == null) return;
que.offer(root);
preOrder(root.left);
preOrder(root.right);
}
// Definition for a binary tree node.
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {
}
TreeNode(int val) {
this.val = val;
}
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
}
2、处理好任意一颗子树
进阶:O(1)的空间复杂度即原地算法,原地修改left/right,原地展开。
target:将二叉树展开为单向链表,即left=null,right指向后继。
后继的定义为前序遍历的后继。
O(1)空间复杂度:必须得搞清楚每一颗子树是如何处理left/right指针。
-
如果一开始就把right赋值了,那么就存在断链问题。
-
是否可以把right记录下来。接到左孩子的最右节点处,以此来满足前序遍历的前驱后继关系。
-
那么当前子树有四种情况,分别处理。四中情况:无孩子(叶子节点);有一个左孩子;有一个右孩子;有两个孩子。
// 进阶:O(1)的空间复杂度即原地算法,原地修改left/right,原地展开。
class Flatten2 {
/*
target:将二叉树展开为单向链表,即left=null,right指向后继。
后继的定义为前序遍历的后继。
O(1)空间复杂度:必须得搞清楚每一颗子树是如何处理left/right指针。
如果一开始就把right赋值了,那么就存在断链问题。
是否可以把right记录下来。接到左孩子的最右节点处,以此来满足前序遍历的前驱后继关系。
那么当前子树有四种情况,分别处理。四中情况:无孩子(叶子节点);有一个左孩子;有一个右孩子;有两个孩子。
*/
public void flatten(TreeNode root) {
flattenTree(root);
}
private TreeNode flattenTree(TreeNode root) {
// 递归到null节点上,底下再无子树,结束递归。
if (root == null) return null;
// 为了防止断链,先把right保存到当前栈帧里面。
TreeNode right = root.right;
// 修改left/right指针。
root.right = root.left;
root.left = null;
// 往深处进行前序遍历,先左后右,但由于特殊情况,以右再接一个右。
TreeNode left = flattenTree(root.right);
// 四中情况:无孩子(叶子节点);有一个左孩子;有一个右孩子;有两个孩子。
// 两个null,此栈帧里的root为叶子节点,它就是父节点左子树的最右节点。
if (left == null && right == null) return root;
// 左子树的最右节点不为null,右子树为null,直接返回左子树最右节点即可。
if (left != null && right == null) return left;
// 当前root左右孩子都有,则将记录的right接到左孩子的最右节点。最后进行递归修改left/right指针。
if (left != null && right != null) {
left.right = right;
}
else root.right = right;//没有左孩子,只有右孩子,那么就没有什么左子树的最右节点,那个节点就是自己root,把right接回来继续递归修改。
// 继续递归修改,并返回右子树的最右节点,当作root这个子树的最右节点。
return flattenTree(right);
}
// Definition for a binary tree node.
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {
}
TreeNode(int val) {
this.val = val;
}
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
}
总结
- 所有处理树的核心都是处理好任意一颗子树即可,通过处理子树的分析,递归体也就出来了,问题迎刃而解。