代码随想录算法训练营第18天(二叉树5 | 513 找树左下角的值 112. 路径总和 113. 路径总和ii 106.从中序与后序遍历序列构造二叉树,105.从前序与中序遍历序列构造二叉树

513 找树左下角的值

本地递归偏难,反而迭代简单属于模板题, 两种方法掌握一下
题目链接:513 找树左下角的值
文章讲解/视频讲解:513 找树左下角的值

解题思路

递归三部曲:

  1. 确定递归函数的参数和返回值
    参数必须有要遍历的树的根节点,还有就是一个int型的变量用来记录最长深度
    还需要类里的两个全局变量,maxLen用来记录最大深度,result记录最大深度最左节点的数值
  2. 确定终止条件
    当遇到叶子节点的时候,就需要统计一下最大的深度了,所以需要遇到叶子节点来更新最大深度。
  3. 确定单层递归的逻辑
    在找最大深度的时候,递归的过程中依然要使用回溯
    前中后序遍历都可以,因为不涉及到’中’的处理,且都是先处理左子树
// 递归  前中后序遍历都可以,因为不涉及到'中'的处理,且都是先处理左子树
class Solution {
    private int Deep = -1;
    private int value = 0;
    public int findBottomLeftValue(TreeNode root){
        value = root.val;
        findLeftValue(root, 0);
        return value;
    }    

    private void findLeftValue(TreeNode root, int deep){
        if(root == null) return;
        if(root.left == null && root.right == null){
            if(deep > Deep){
                value = root.val;
                Deep = deep;
            }
        }

        if(root.left != null){
            deep++;
            findLeftValue(root.left, deep);
            deep--;  // 回溯体现之处
        } 
        if(root.right != null){
            deep++;
            findLeftValue(root.right, deep);
            deep--;  // 回溯体现之处
        } 
    }
}
 // 层序  迭代
class Solution {
    // 迭代方式 借助队列
    public int findBottomLeftValue(TreeNode root){
        Deque<TreeNode> que = new LinkedList<>(); //辅助队列
        if (root == null) {
            return 0;
        }
        int res = 0;
        que.offer(root);  // 根节点入队
        while (!que.isEmpty()) {
            int len = que.size();
            for(int i = 0; i < len; i++) {
                TreeNode peek = que.poll(); //当前层元素依次出队

                if(i == 0) res = peek.val;  // res每次会被当前层最左边的值覆盖
                // 下一层元素入队
                if (peek.left != null) que.offer(peek.left);
                if (peek.right != null) que.offer(peek.right);
            }            
        }
        return res;
    }    
}

112. 路径总和 113. 路径总和ii

本题 又一次设计要回溯的过程,而且回溯的过程隐藏的还挺深,建议先看视频来理解
题目链接:112. 路径总和
文章讲解/视频讲解:112. 路径总和
从中序与后序遍历序列构造二叉树
本题算是比较难的二叉树题目了,大家先看视频来理解。
前中后序遍历都可以,因为不涉及到’中’的处理

112 解题思路

递归三部曲

  1. 确定递归函数的参数和返回类型
    参数:需要二叉树的根节点,还需要一个计数器,这个计数器用来计算二叉树的一条边之和是否正好是目标和,计数器为int型。
    本题我们要找一条符合条件的路径,所以递归函数需要返回值,及时返回
  2. 确定终止条件
    首先计数器如何统计这一条路径的和呢?
    不要去累加然后判断是否等于目标和,那么代码比较麻烦,可以用递减,让计数器count初始为目标和,然后每次减去遍历路径节点上的数值。
    如果最后count == 0,同时到了叶子节点的话,说明找到了目标和。
    如果遍历到了叶子节点,count不为0,就是没找到。
  3. 确定单层递归的逻辑
    因为终止条件是判断叶子节点,所以递归的过程中就不要让空节点进入递归了。
    递归函数是有返回值的,如果递归函数返回true,说明找到了合适的路径,应该立刻返回。

113解题思路

113.路径总和ii要遍历整个树,找到所有路径,所以递归函数不要返回值!

总结

通过112. 路径总和113. 路径总和ii 要明确递归函数什么时候需要返回值,什么不需要返回值。

  • 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。(113.路径总和ii
  • 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。 (236. 二叉树的最近公共祖先
  • 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(112.路径总和
// 112
// 递归+回溯
// 易懂版本
class Solution {
    public boolean hasPathSum(TreeNode root, int targetSum) {
        if(root == null) return false;
        // 叶子节点
        if(root.left == null && root.right == null) return targetSum == root.val;
        // 左子树遍历
        if(root.left != null){
            targetSum -= root.val;
            if(hasPathSum(root.left, targetSum)) return true; // 找到
            targetSum += root.val;  // 回溯
        }
        // 右子树遍历
        if(root.right != null){
            targetSum -= root.val;
            if(hasPathSum(root.right, targetSum)) return true; // 找到
            targetSum += root.val;  // 回溯
        }
        return false;
    }
}
// 112
// 递归+回溯
// 简洁版本
class solution {
    public boolean haspathsum(treenode root, int targetsum) {

        if (root == null) return false; // 为空退出

        // 叶子节点判断是否符合
        if (root.left == null && root.right == null) return root.val == targetsum;

        // 求两侧分支的路径和
        return haspathsum(root.left, targetsum - root.val) || haspathsum(root.right, targetsum - root.val);
    }
}
// 113
// 递归+回溯
// 易懂版本
class Solution {
    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        List<List<Integer>> res = new ArrayList<>();
        if(root == null) return res;
        List<Integer> path = new LinkedList<>();
        preorderdfs(root, targetSum, res, path);
        return res;
    }
    public void preorderdfs(TreeNode root, int targetSum, List<List<Integer>> res, List<Integer> path){
        path.add(root.val);
        //遍历到叶子节点
        if(root.left == null && root.right == null){
            if(root.val == targetSum) res.add(new ArrayList<>(path));
            return;  // 向上返回,继续后续查找
        }
        // 左
        if(root.left != null){
            targetSum -= root.val;
            preorderdfs(root.left, targetSum, res, path);
            targetSum += root.val;  // 回溯
            path.remove(path.size() - 1);  // 回溯
        }
        // 右
        if(root.right != null){
            targetSum -= root.val;
            preorderdfs(root.right, targetSum, res, path);
            targetSum += root.val;  // 回溯
            path.remove(path.size() - 1);  // 回溯
        }
    }
}
// 113
// 递归+回溯
// 简洁版本
class Solution {
    List<List<Integer>> result;
    LinkedList<Integer> path;
    public List<List<Integer>> pathSum (TreeNode root,int targetSum) {
        result = new LinkedList<>();
        path = new LinkedList<>();
        travesal(root, targetSum);
        return result;
    }
    private void travesal(TreeNode root,  int count) {
        if (root == null) return;
        path.offer(root.val);
        count -= root.val;
        if (root.left == null && root.right == null && count == 0) {
            result.add(new LinkedList<>(path));
        }
        travesal(root.left, count);
        travesal(root.right, count);
        path.removeLast(); // 回溯
    }
}

106.从中序与后序遍历序列构造二叉树,105.从前序与中序遍历序列构造二叉树

本题算是比较难的二叉树题目了
题目链接:106.从中序与后序遍历序列构造二叉树
文章讲解/视频讲解:106.从中序与后序遍历序列构造二叉树

解题思路

第一步:如果数组大小为零的话,说明是空节点了。
第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点
第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)(根据后序数组所确定的根节点)
第五步:切割后序数组,切成后序左数组和后序右数组(根据中序数组所确定的左右区间)
第六步:递归处理左区间和右区间

注意点:确定切割的标准,是左闭右开,还有左开右闭,还是左闭右闭,这个就是不变量,要在递归中保持这个不变量。

前序和后序不能唯一确定一棵二叉树!,因为没有中序遍历无法确定左右部分,也就是无法分割。

每层递归定义了新的数组,既耗时又耗空间,但代码是最好理解的。
用下标索引写出的代码版本,每次用下标索引来分割,这样效率会比较高。

// 106
// 递归1
class Solution {
    Map<Integer, Integer> map;  // 方便根据数值查找位置
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        map = new HashMap<>();
        for (int i = 0; i < inorder.length; i++) { // 用map保存中序序列的数值对应位置
            map.put(inorder[i], i);
        }

        return findNode(inorder,  0, inorder.length, postorder,0, postorder.length);  // 前闭后开
    }

    public TreeNode findNode(int[] inorder, int inBegin, int inEnd, int[] postorder, int postBegin, int postEnd) {
        // 参数里的范围都是前闭后开
        if (inBegin >= inEnd || postBegin >= postEnd) {  // 1. 已经没有元素
            return null;
        }
        int rootIndex = map.get(postorder[postEnd - 1]);  // 2. 先根据后序找到根节点  // 3. 在中序遍历中找到根节点位置
        TreeNode root = new TreeNode(inorder[rootIndex]);  // 构造结点
        int lenOfLeft = rootIndex - inBegin;  // 保存中序左子树个数,用来确定后序数列的个数
        // 4.切割中序数组,确定中序遍历的左右子树
        // 5. 切割后序数组,确定后序遍历的左右子树
        // 6. 递归处理左右区间
        root.left = findNode(inorder, inBegin, rootIndex,
                            postorder, postBegin, postBegin + lenOfLeft);
        root.right = findNode(inorder, rootIndex + 1, inEnd,
                            postorder, postBegin + lenOfLeft, postEnd - 1);

        return root;
    }
}
// 106
// 易懂 递归2
class Solution {
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        if(postorder.length == 0 || inorder.length == 0) return null; 
        return buildHelper(inorder, 0, inorder.length, postorder, 0, postorder.length); // 保持左闭右开
    }
    private TreeNode buildHelper(int[] inorder, int inorderStart, int inorderEnd, int[] postorder, int postorderStart, int postorderEnd){
        if(postorderStart == postorderEnd) return null; // 1. 已经没有元素
        int rootVal = postorder[postorderEnd - 1];  // 2. 先根据后序找到根节点
        TreeNode root = new TreeNode(rootVal);
        // 3. 在中序遍历中找到根节点位置
        int middleIndex;
        for(middleIndex = inorderStart; middleIndex < inorderEnd; middleIndex++){
            if(inorder[middleIndex] == rootVal) break;
        }
        // 4.切割中序数组,确定中序遍历的左右子树
        int leftInorderStart = inorderStart; 
        int leftInorderEnd = middleIndex;
        int rightInorderStart = middleIndex + 1;
        int rightInorderEnd = inorderEnd;

        // 5. 切割后序数组,确定后序遍历的左右子树
        int leftPostorderStart = postorderStart;
        int leftPostorderEnd = postorderStart + (middleIndex - inorderStart);
        int rightPostorderStart = leftPostorderEnd;
        int rightPostorderEnd = postorderEnd - 1;

        // 6. 递归处理左右区间
        root.left = buildHelper(inorder, leftInorderStart, leftInorderEnd,  postorder, leftPostorderStart, leftPostorderEnd);
        root.right = buildHelper(inorder, rightInorderStart, rightInorderEnd, postorder, rightPostorderStart, rightPostorderEnd);

        return root;
    }
}
// 105
// 效率高一些 递归
class Solution {
    Map<Integer, Integer> map;  // 方便根据数值查找位置
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        map = new HashMap<>();
        for (int i = 0; i < inorder.length; i++) { // 用map保存中序序列的数值对应位置
            map.put(inorder[i], i);
        }

        return findNode(preorder,  0, preorder.length, inorder,0, inorder.length);  // 前闭后开
    }

    public TreeNode findNode(int[] preorder, int preBegin, int preEnd, int[] inorder, int inBegin, int inEnd) {
        // 参数里的范围都是前闭后开
        if (preBegin >= preEnd || inBegin >= inEnd) {  // 1. 已经没有元素
            return null;
        }
        int rootIndex = map.get(preorder[preBegin]);  // 2. 先根据后序找到根节点  // 3. 在中序遍历中找到根节点位置
        TreeNode root = new TreeNode(inorder[rootIndex]);  // 构造结点
        int lenOfLeft = rootIndex - inBegin;  // 保存中序左子树个数,用来确定后序数列的个数
        // 4.切割中序数组,确定中序遍历的左右子树
        // 5. 切割后序数组,确定后序遍历的左右子树
        // 6. 递归处理左右区间
        root.left = findNode(preorder, preBegin + 1, preBegin + lenOfLeft + 1,
                            inorder, inBegin, rootIndex);
        root.right = findNode(preorder, preBegin + lenOfLeft + 1, preEnd,
                            inorder, rootIndex + 1, inEnd);

        return root;
    }
}
  • 22
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值