代码随想录算法训练营day18|513找树左下角的值 |112 113路径之和| 106 105

513.找树左下角的值

力扣题目链接

给定一个二叉树,在树的最后一行找到最左边的值。

//层序遍历
class Solution {
    public int findBottomLeftValue(TreeNode root) {
        Queue<TreeNode> q=new LinkedList<>();
        if(root!=null) q.offer(root);
        int res=0;
        while(!q.isEmpty()){
            int len=q.size();
            for(int i=0;i<len;i++){
                TreeNode node=q.poll();
                if(i==0){   // 记录最后一行第一个元素
                    res=node.val;
                }
                if(node.left!=null){
                    q.offer(node.left);
                }
                if(node.right!=null){
                    q.offer(node.right);
                }
            }

        }
        return res;
     }

递归怎么做呢

**求深度最大的叶子节点 **

左下角的值不一定是左孩子

那么如何找最左边的呢?可以使用前序遍历(当然中序,后序都可以,因为本题没有 中间节点的处理逻辑,只要左优先就行),保证优先左边搜索,然后记录深度最大的叶子节点,此时就是树的最后一行最左边的值

递归三部曲:

确定递归函数的参数和返回值

参数必须有要遍历的树的根节点,还有就是一个int型的变量用来记录最长深度。 这里就不需要返回值了,所以递归函数的返回类型为void。

本题还需要类里的两个全局变量,maxLen用来记录最大深度,result记录最大深度最左节点的数值。

代码如下:

 
int maxDeep = -1;  //记录二叉树的最大深度
int res= 0;    //记录节点里的值
void findLeftValue (TreeNode root,int deep) //这里得deep是当前记录深度

确定终止条件

当遇到叶子节点的时候,就需要统计一下最大的深度了,所以需要遇到叶子节点来更新最大深度。

代码如下:

 if (root == null) return;
 if (root.left == null && root.right == null) {  //遍历到叶子节点
       if (deep > Deep) {  //统计最大深度
       res = root.val;
       Deep = deep;
    }
}

确定单层递归的逻辑

在找最大深度的时候,递归的过程中依然要使用回溯,代码如下:

if (root.left!=null) {
     depth++;
     traversal(root->left, depth);
     depth--; // 回溯
}
if (root.right!=null) {
      depth++;
      traversal(root->right, depth);
      depth--; // 回溯
}
      

完整代码

class Solution {
    int maxDeep=-1;
    int res=0;
    public int findBottomLeftValue(TreeNode root) {
        findLeftValue(root,0);
        return res;
        
    }
    public void findLeftValue(TreeNode root,int deep){
        if(root==null) return;
        if(root.left==null&&root.right==null){
            if(deep>maxDeep){
                maxDeep=deep;
                res=root.val;
            }
        }
        //处理左
        if(root.left!=null){
            deep++;  //向左遍历 深度加一
            findLeftValue(root.left,deep);
            deep--; //回溯 这里的回溯是因为当前的深度就是deep,只加不减其实改变了当前的深度,而且后面需要继续操作右子树,如果改变了当前的深度,右子树的深度也会收到影响   

        }
         //处理右
        if(root.right!=null){
            deep++;
            findLeftValue(root.right,deep);
            deep--; //回溯

        }
        return;
    }
}




//精简版
class Solution {
    int maxDeep=-1;
    int res=0;
    public int findBottomLeftValue(TreeNode root) {
        findLeftValue(root,0);
        return res;
        
    }
    public void findLeftValue(TreeNode root,int deep){
        if(root==null) return;
        if(root.left==null&&root.right==null){
            if(deep>maxDeep){
                maxDeep=deep;
                res=root.val;
            }
        }
        if(root.left!=null) findLeftValue(root.left,deep+1);
        if(root.right!=null) findLeftValue(root.right,deep+1);
        return;
    }
}

112. 路径总和

力扣题目链接

给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。

说明: 叶子节点是指没有子节点的节点。

思路:

可以使用深度优先遍历的方式(本题前中后序都可以,无所谓,因为中节点也没有处理逻辑)来遍历二叉树

确定递归函数的参数和返回类型

参数:需要二叉树的根节点,还需要一个计数器,这个计数器用来计算二叉树的一条边之和是否正好是目标和,计数器为int型。

再来看返回值,递归函数什么时候需要返回值?什么时候不需要返回值?这里总结如下三点:

  • 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。(这种情况就是本文下半部分介绍的113.路径总和ii)
  • 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。 (这种情况我们在236. 二叉树的最近公共祖先中介绍)
  • 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(本题的情况)

而本题我们要找一条符合条件的路径,所以递归函数需要返回值,及时返回,那么返回类型是什么呢?

如图所示:

112.路径总和

图中可以看出,遍历的路线,并不要遍历整棵树,所以递归函数需要返回值,可以用bool类型表示。

确定终止条件

首先计数器如何统计这一条路径的和呢?

不要去累加然后判断是否等于目标和,那么代码比较麻烦,可以用递减,让计数器count初始为目标和,然后每次减去遍历路径节点上的数值。

如果最后count == 0,同时到了叶子节点的话,说明找到了目标和。

如果遍历到了叶子节点,count不为0,就是没找到。

递归终止条件代码如下:

if (cur.left==null && cur.right==null && count == 0) return true;//遇到叶子节点,并且计数为0
if (cur.left==null && cur.right==null) return false; // 遇到叶子节点直接返回

确定单层递归的逻辑

因为终止条件是判断叶子节点,所以递归的过程中就不要让空节点进入递归了。

递归函数是有返回值的,如果递归函数返回true,说明找到了合适的路径,应该立刻返回。

为了把回溯的过程体现出来,可以改为如下代码:

		if (cur.left!=null) { // 左
            count -= cur.left.val; // 递归,处理节点;
            if (traversal(cur.left, count)) return true;
            count += cur.left.val; // 回溯,撤销处理结果
        }
        if (cur.right!=null) { // 右
            count -= cur.right.val; // 递归,处理节点;
            if (traversal(cur.right, count)) return true;
            count += cur.right.val; // 回溯,撤销处理结果
        }
        return false;

完整java代码

class Solution {
    public boolean hasPathSum(TreeNode root, int targetSum) {
        if (root == null) return false;
        return traversal(root, targetSum - root.val);
    }
    
    boolean traversal(TreeNode cur, int count) {
        if (cur.left==null && cur.right==null && count == 0) return true; // 遇到叶子节点,并且计数为0
        if (cur.left==null && cur.right==null) return false; // 遇到叶子节点直接返回

        if (cur.left!=null) { // 左
            count -= cur.left.val; // 递归,处理节点;
            if (traversal(cur.left, count)) return true;
            count += cur.left.val; // 回溯,撤销处理结果
        }
        if (cur.right!=null) { // 右
            count -= cur.right.val; // 递归,处理节点;
            if (traversal(cur.right, count)) return true;
            count += cur.right.val; // 回溯,撤销处理结果
        }
        return false;
    }
}


//精简版  但这玩意我一下就看懂了 很离谱
class Solution {
    public boolean hasPathSum(TreeNode root, int targetSum) {
     if (root == null) return false; // 为空退出

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

        // 求两侧分支的路径和
     return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val);
    }
}

113. 路径总和ii

力扣题目链接

和上面那道思路差不多,但是需要返回值

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) {
            // 找到了和为 targetsum 的路径
            if (targetsum - root.val == 0) {
                res.add(new ArrayList<>(path));
            }
            return; // 如果和不为 targetsum,返回
        }

        if (root.left != null) {
            preorderdfs(root.left, targetsum - root.val, res, path);
            path.remove(path.size() - 1); // 回溯
        }
        if (root.right != null) {
            preorderdfs(root.right, targetsum - root.val, res, path);
            path.remove(path.size() - 1); // 回溯
        }
    }
}

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

力扣题目链接

根据一棵树的中序遍历与后序遍历构造二叉树。

注意: 你可以假设树中没有重复的元素。

例如,给出

  • 中序遍历 inorder = [9,3,15,20,7]
  • 后序遍历 postorder = [9,15,7,20,3] 返回如下的二叉树:

106. 从中序与后序遍历序列构造二叉树1

梦回数据结构考试

流程如图:

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

那么代码应该怎么写呢?

说到一层一层切割,就应该想到了递归。

来看一下一共分几步:

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

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

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) {  // 不满足左闭右开,说明没有元素,返回空树
            return null;
        }
        int rootIndex = map.get(postorder[postEnd - 1]);  // 找到后序遍历的最后一个元素在中序遍历中的位置
        TreeNode root = new TreeNode(inorder[rootIndex]);  // 构造结点
        int lenOfLeft = rootIndex - inBegin;  // 保存中序左子树个数,用来确定后序数列的个数
        root.left = findNode(inorder, inBegin, rootIndex,
                            postorder, postBegin, postBegin + lenOfLeft);
        root.right = findNode(inorder, rootIndex + 1, inEnd,
                            postorder, postBegin + lenOfLeft, postEnd - 1);

        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) {  // 不满足左闭右开,说明没有元素,返回空树
            return null;
        }
        int rootIndex = map.get(preorder[preBegin]);  // 找到前序遍历的第一个元素在中序遍历中的位置
        TreeNode root = new TreeNode(inorder[rootIndex]);  // 构造结点
        int lenOfLeft = rootIndex - inBegin;  // 保存中序左子树个数,用来确定前序数列的个数
        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;
    }
}

11

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值