面试题32:从上到下打印二叉树
题目一:不分行从上到下打印

- 从上到下打印,其实就是二叉树的层次遍历。

- 从根节点开始,下一层的两个节
6,10点应该加入容器中。 - 然后先取出容器中的节点
6,然后其子节点5,7加入容器。这时容器中有5,7,10,应该下一个取出的是10。 - 不难看出,我们应该使用队列作为容器。
- 层次遍历的递归,只能分行打印!
- 非递归: 直接取出队列中的元素加入最后的结果集合就可以。
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
ArrayList<Integer> result = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
if (root == null) {// 要访问root.val,提前进行特殊情况处理
return result;
}
queue.add(root);
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
result.add(node.val);
// 将非空的左右孩子节点加入到队列中
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
}
return result;
}
题目二:分行从上到下打印二叉树

- 与题目一相比,每一层应该单独存储,这时需要考虑层次问题。
- 迭代的改进:
- 每次循环不能只取出队列中的第一个元素,而是应该一次性取出该层的所有元素。
- 每层的所有元素个数,应该是当前队列的大小!
- 代码如下:
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
if (root == null) {
return result;
}
queue.add(root);
int level = 0;// 记录哪一层
while (!queue.isEmpty()) {
int size = queue.size();
// 添加新的动态数组,存储该层的节点值
result.add(new ArrayList<>());
// 取出一层的节点
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
result.get(level).add(node.val);
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
}
level++;// 更新遍历的层次
}
return result;
}
- 递归实现:
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
if (root == null) {
return result;
}
helper(root, 0, result);
return result;
}
public void helper(TreeNode node, int level, List<List<Integer>> result) {
if (result.size() == level) {// 新建动态数组,按层存储
result.add(new ArrayList<>());
}
result.get(level).add(node.val);
if (node.left != null) {
helper(node.left, level + 1, result);
}
if (node.right != null) {
helper(node.right, level + 1, result);
}
}
③ 题目三:之字形打印二叉树

- 所谓的之字形打印,就是偶数行按照从左到右的顺序,奇数行按照从右到左的顺序(从0开始计算)。
- 可以在之前的分行打印的基础上,通过判断
level的奇偶性就可以实现。 - 代码如下:
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
Queue<TreeNode> queue = new LinkedList<>();
if (root == null) {
return result;
}
int level = 0;
queue.add(root);
while (!queue.isEmpty()) {
int size = queue.size();
result.add(new ArrayList<>());
for (int i = 0; i < size; i++) {
TreeNode node = queue.poll();
if (level % 2 == 0) {
result.get(level).add(node.val);// 尾插法
} else {
result.get(level).add(0, node.val);// 头插法
}
if (node.left != null) {
queue.add(node.left);
}
if (node.right != null) {
queue.add(node.right);
}
}
level++;
}
return result;
}
④ 使用栈实现二叉树的之字形打印
- 分析过程如下:
- 打印根节点时,将左右子树阿计入容器。
- 打印第二层时,先打印右子树,再打印左子树。(可以考虑使用栈,先进先出)
- 打印第二层的节点时,如果继续使用栈,应该先加入右子树再加入左子树。
- 这样,打印第三层是,顺序弹出的节点才是从左到右的顺序。
- 使用栈,但是考虑到,如果打印第二层的节点时,同时加入了其子节点,将会导致弹出的是第三层的节点。因此,需要使用两个栈,一个存储奇数层的节点,一个存储偶数层的节点。
- 代码如下:
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> result = new ArrayList<>();
Stack<TreeNode> stack1 = new Stack<>();
Stack<TreeNode> stack2 = new Stack<>();
if (root == null) {
return result;
}
int level = 0;
stack1.push(root);
while (true) {
if (stack1.isEmpty() && stack2.isEmpty()) {
break;
} else {// 根据层次的奇偶性打印二叉树
//创建新的动态数组,容纳新的层次
result.add(new ArrayList<>());
// 弹出栈中的所有元素,在stack2中按照先左后右的顺序添加节点
if (level % 2 == 0) {
while (!stack1.isEmpty()) {
TreeNode node = stack1.pop();
result.get(level).add(node.val);
if (node.left != null) {
stack2.push(node.left);
}
if (node.right != null) {
stack2.push(node.right);
}
}
} else {// 弹出stack2中所有元素,实现从右到左打印;按照先右后左的顺序向stack1中添加节点
while (!stack2.isEmpty()) {
TreeNode node = stack2.pop();
result.get(level).add(node.val);
if (node.right != null) {
stack1.push(node.right);
}
if (node.left != null) {
stack1.push(node.left);
}
}
}
level++;
}
}
return result;
}
面试题33:二叉树的后续遍历序列


- 分析:
- 后序遍历的特点:根节点在最后,前面的序列是左子树和右子树的。
- 二叉搜索树,左子树的值都应该小于根节点,右子树的值都应该大于根节点。
- 结合上面两点,可以将序列划分成左子树(有可能没有)、右子树、根节点。
- 递归对左右子树进行判断,最终就可以直到是否为二叉树的后续序列。
- 上面的
7,4,5,6,根节点为6。查找左子树时,发现第一个元素的值就大于6,说明只有右子树。然后发现整个右子树序列中的元素(如4)不满足大于根节点的条件,说明这不是二叉查找树的后序序列。

- 根据分析,代码如下。注意: 如果序列长度为0,说明是空树,系统要求返回
false。
public boolean VerifySquenceOfBST(int[] sequence) {
// 长度为零是一棵空的二叉搜索树,直接返回false
if (sequence.length == 0) {
return false;
}
return helper(sequence, 0, sequence.length - 1);
}
public boolean helper(int[] sequence, int start, int end) {
// 长度<=1,说明已经是最小序列,不用判断返回true
if (end - start <= 1) {
return true;
}
int cur = start;
//查找左子树的end
for (; cur < end; cur++) {
if (sequence[cur] > sequence[end]) {
break;
}
}
int end1 = cur;
// 判断右子树节点是否都大于根节点`在这里插入代码片`
while (cur < end && sequence[cur] > sequence[end]) {
cur++;
}
if (cur != end) {// 没到end-1,右子树不合法
return false;
}
// 分别对左右子树进行验证
return helper(sequence, start, end1 - 1) && helper(sequence, end1, end - 1);
}
变形 —— leetcode255:判断是否为二叉搜索树的前序序列

- 与原题不同的是:前序遍历,第一个元素是根节点的值。
- 代码如下:
public static boolean isValidBST(int[] sequence) {
// 判断题系统不认为空树是false,无需进行特殊处理
return helper(sequence, 0, sequence.length - 1);
}
public static boolean helper(int[] sequence, int start, int end) {
if (end - start <= 1) {// 合法序列,不用再判断
return true;
}
// 查找左子树的end
int cur = start + 1;
for (; cur <= end; cur++) {
if (sequence[cur] > sequence[0]) {
break;
}
}
int end1 = cur;// 记录左子树的end
// 判断右子树是否合法
while (cur <= end && sequence[cur] > sequence[0]) {
cur++;
}
if (cur <= end) {// 右子树不合法
return false;
}
return helper(sequence, start + 1, end1 - 1) && helper(sequence, end1, end);
}
变形 —— leetcode98:判断是否为二叉查找树
- 二叉搜索树的中序遍历严格升序:
public boolean isValidBST(TreeNode root) {
List<Integer> result = new ArrayList<>();
InOrder(root, result);
// 判断中序序列是否升序
for (int i = 0; i < result.size() - 1; i++) {
if (result.get(i) >= result.get(i + 1)) {
return false;
}
}
return true;
}
public void InOrder(TreeNode node, List<Integer> result) {
if (node == null) {
return;
}
InOrder(node.left, result);
result.add(node.val);
InOrder(node.right, result);
}
- 递归:
- 二叉搜索树的每个节点都有上限和下限,左孩子的上限是父节点,下限继承父节点的下限;右孩子的下限是父节点,上限继承父节点的上限。
- 根节点的上限为
Integer.MAX_VALUE,下限为Integer.MIN_VALUE。 - 可以通过递归的方式,判断所有节点是否满足上下限。
public boolean isValidBST(TreeNode root) {
// 发现根节点的值超过int范围,使用long
return isLimitted(root, Long.MIN_VALUE, Long.MAX_VALUE);
}
public boolean isLimitted(TreeNode root, long min, long max) {
if (root == null) {// 为空,不用比较,直接返回true
return true;
}
if (root.val >= max || root.val <= min) {
return false;
}
// 当前节点满足要求,继续判断其左右孩子节点,直到为null或不满足要求
return isLimitted(root.left, min, root.val) &&
isLimitted(root.right, root.val, max);
}
面试题:34:二叉树中和为某一值的路径

- 路径: 必须是从根节点到某一叶子节点,而非任意节点到任意节点。
- 分析:
- 每次都先访问根节点,将根节点加入路径中。
- 如果不满足要求,就继续访问左右孩子节点;如果满足要求,说明找到一条路径,先加入到结果集中。
- 每次访问完当前节点后,应该回溯访问上一节点的其他可能路径:删除路径中的当前节点。
- 代码如下:
List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int sum) {
List<Integer> path = new ArrayList<>();// 存储可能的路径
backtrace(root, sum, path);
return result;
}
public void backtrace(TreeNode root, int target, List<Integer> path) {
if (root == null) {
return;
}
// 尝试添加当前节点到路径中
path.add(root.val);
target = target - root.val;
// 满足目标和且是叶子节点,说明找到一条路径
if (target == 0 && root.left == null && root.right == null) {
result.add(new ArrayList<>(path));
} else {// 不满足要求,继续访问孩子节点
backtrace(root.left, target, path);
backtrace(root.right, target, path);
}
// 去除当前节点,回到上一层,查找另一种可能的路径
path.remove(path.size() - 1);
}
变形—— leetcode112:判断二叉树中是否存在指定目标和的路径
- 分析:
- 该不是需要找出所有满足条件的路径,而是判断是否存在满足条件的路径。即只需要有一条满足条件的路径就可以退出递归。
- 如果当前节点为
null,直接返回false;遇到当前节点满足要求,直接返回true;当前节点不满足要求,继续递归查找其左右孩子节点,只要某条路径满足条件即可。 - 因此最后的判断条件为
return helper(root.left, target) || helper(root.right, target);
- 代码如下:
public boolean hasPathSum(TreeNode root, int sum) {
return helper(root, sum);
}
public boolean helper(TreeNode root, int target) {
if (root == null) {
return false;
}
target = target - root.val;
if (target == 0 && root.left == null && root.right == null) {
return true;
}
// 当前节点不满足,继续查找左右子树是否存在满足条件的路径
return helper(root.left, target) || helper(root.right, target);
}
变形 —— leetcode124:二叉树中的最大路径和
- 对于一个非空节点,最大路径和无非是下面几种情况:
- 当前节点本身
- 当前节点 + 左子树
- 当前节点 + 右子树
- 当前节点 + 左子树 + 右子树
- 不停的更新全局变量max,最后得到是二叉树中的最大路径和。
- 代码如下:
int max = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
getSum(root);// 递归求解最大路径和
return max;
}
public int getSum(TreeNode root) {
if (root == null) {
return 0;
}
int left = getSum(root.left);// 左子树的最大路径和
int right = getSum(root.right);// 右子树的最大路径和
// 可以向上发展父节点的最大路径和
int temp = Math.max(root.val, Math.max(root.val + left, root.val + right));
// 当前节点可以获取的最大路径和
int curMax = Math.max(temp, root.val + left + right);
if (max < curMax) {
max = curMax;
}
return temp;// 返回temp,继续向上发展父节点
}
本文深入探讨了二叉树的多种遍历方式,包括层次、之字形及后序遍历,并详细解析了如何寻找特定路径和最大路径和等问题。通过递归与非递归算法,提供了丰富的代码实例。
&spm=1001.2101.3001.5002&articleId=101368591&d=1&t=3&u=4d19ee772fb6470e854c25dd0a1380da)
2292

被折叠的 条评论
为什么被折叠?



