剑指offer_面试题32:从上到下打印二叉树,面试题33:二叉树的后续遍历序列(二叉树的路径和相关题型)

本文深入探讨了二叉树的多种遍历方式,包括层次、之字形及后序遍历,并详细解析了如何寻找特定路径和最大路径和等问题。通过递归与非递归算法,提供了丰富的代码实例。
面试题32:从上到下打印二叉树
题目一:不分行从上到下打印

在这里插入图片描述

  • 从上到下打印,其实就是二叉树的层次遍历。
    在这里插入图片描述
  1. 从根节点开始,下一层的两个节6,10点应该加入容器中。
  2. 然后先取出容器中的节点6,然后其子节点5,7加入容器。这时容器中有5,7,10,应该下一个取出的是10
  3. 不难看出,我们应该使用队列作为容器。
  • 层次遍历的递归,只能分行打印!
  • 非递归: 直接取出队列中的元素加入最后的结果集合就可以。
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;
}
题目二:分行从上到下打印二叉树

在这里插入图片描述

  • 与题目一相比,每一层应该单独存储,这时需要考虑层次问题。
  • 迭代的改进:
  1. 每次循环不能只取出队列中的第一个元素,而是应该一次性取出该层的所有元素。
  2. 每层的所有元素个数,应该是当前队列的大小!
  • 代码如下:
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;
}
④ 使用栈实现二叉树的之字形打印
  • 分析过程如下:
  1. 打印根节点时,将左右子树阿计入容器。
  2. 打印第二层时,先打印右子树,再打印左子树。(可以考虑使用栈,先进先出)
  3. 打印第二层的节点时,如果继续使用栈,应该先加入右子树再加入左子树。
  4. 这样,打印第三层是,顺序弹出的节点才是从左到右的顺序。
  • 使用栈,但是考虑到,如果打印第二层的节点时,同时加入了其子节点,将会导致弹出的是第三层的节点。因此,需要使用两个栈,一个存储奇数层的节点,一个存储偶数层的节点。
  • 代码如下:
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:二叉树的后续遍历序列

在这里插入图片描述
在这里插入图片描述

  • 分析:
  1. 后序遍历的特点:根节点在最后,前面的序列是左子树和右子树的。
  2. 二叉搜索树,左子树的值都应该小于根节点,右子树的值都应该大于根节点。
  3. 结合上面两点,可以将序列划分成左子树(有可能没有)、右子树、根节点。
  4. 递归对左右子树进行判断,最终就可以直到是否为二叉树的后续序列。
  • 上面的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);
}
  • 递归:
  1. 二叉搜索树的每个节点都有上限和下限,左孩子的上限是父节点,下限继承父节点的下限;右孩子的下限是父节点,上限继承父节点的上限。
  2. 根节点的上限为Integer.MAX_VALUE,下限为Integer.MIN_VALUE
  3. 可以通过递归的方式,判断所有节点是否满足上下限。
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:二叉树中和为某一值的路径

在这里插入图片描述

  • 路径: 必须是从根节点某一叶子节点,而非任意节点到任意节点。
  • 分析:
  1. 每次都先访问根节点,将根节点加入路径中。
  2. 如果不满足要求,就继续访问左右孩子节点;如果满足要求,说明找到一条路径,先加入到结果集中。
  3. 每次访问完当前节点后,应该回溯访问上一节点的其他可能路径:删除路径中的当前节点。
  • 代码如下:
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:判断二叉树中是否存在指定目标和的路径
  • 分析:
  1. 该不是需要找出所有满足条件的路径,而是判断是否存在满足条件的路径。即只需要有一条满足条件的路径就可以退出递归。
  2. 如果当前节点为null,直接返回false;遇到当前节点满足要求,直接返回true;当前节点不满足要求,继续递归查找其左右孩子节点,只要某条路径满足条件即可。
  3. 因此最后的判断条件为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:二叉树中的最大路径和
  • 对于一个非空节点,最大路径和无非是下面几种情况:
  1. 当前节点本身
  2. 当前节点 + 左子树
  3. 当前节点 + 右子树
  4. 当前节点 + 左子树 + 右子树
  • 不停的更新全局变量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,继续向上发展父节点
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值