代码随想录算法训练营第16天 | 找树左下角的值、路径总和、从中序与后序遍历序列构造二叉树

513. 找树左下角的值

题目链接/文章讲解/视频讲解
本题递归偏难,反而迭代简单属于模板题, 两种方法掌握一下

思路

  • 求最后一行最靠左侧的值。迭代法更简单。
  • 如何用递归法求左下角的值?如果一直向左遍历,最左边的不一定是在最后一行。要求最后一行,就是深度最大的叶子节点,那么如何求最左侧的值?
  • 本题使用前中后序都可以。因为本题中没有中节点的处理逻辑,只要优先遍历左节点就可以,能保证求到最后一行深度最大的最靠左侧的叶子节点。
  • 注意概念,靠左侧不一定是左孩子,比如节点5这里是最靠左下角的


    示意

伪代码

int maxDepth = INT_MIN;   // 全局变量 记录最大深度
int result;       // 全局变量 最大深度最左节点的数值
private int findBottomLeftValue(Treenode root){
        traversal(root, 0);
        return result;
}
private void traversal(TreeNode root,int depth) {
        //终止条件
        if(root.left == null && root.right == null){
            //进行比较
            if(depth > maxDepth){
                maxDepth = depth;
                result = root.val;
            }
            return;
        }
        //单层逻辑
        if(root.left != null){
            depth++;
            traversal(root.left);
            depth--; //这里要回溯,回溯就在递归的下面、这里要回溯是因为要回退到根节点,重新向右遍历;
        }
        //可以简化为if (root.left != null) findLeftValue(root.left,deep + 1);  因为实际上depth+1并没有改变depth的值
        //右边遍历和逻辑和左边一样,这里不写了
        if(root.right != null){
            depth++;
            traversal(root.right);
            depth--
        }
        return;
       //没有中序,因为不必要
}

用传参数回退的流程:
比如有这样一棵树:


example

完整流程
初始调用:findLeftValue(1, 0)
递归到左子树:findLeftValue(2, 1)
递归到更左子树:findLeftValue(4, 2)
尝试递归更左子树:findLeftValue(null, 3),立即返回。

返回后,在第三次调用 findLeftValue(4, 2) 中继续执行:
尝试递归右子树:findLeftValue(null, 3),立即返回。

返回后,第二次调用 findLeftValue(2, 1) 中继续执行:
尝试递归右子树:findLeftValue(null, 2),立即返回。

返回后,第一次调用 findLeftValue(1, 0) 中继续执行:
递归到右子树:findLeftValue(3, 1)
依次类推,整个过程按照这种逻辑递归和回溯。

/**
 * 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;
 *     }
 * }
 */
class Solution {
    int maxDepth = -1;
    int result = 0;
    public int findBottomLeftValue(TreeNode root) {
         traversal(root, 0);
            return result;
    }
    private void traversal(TreeNode root,int depth) {
        if(root == null) return;
        if(root.left == null && root.right == null){
            if(depth > maxDepth){
                maxDepth = depth;
                result = root.val;
            }
        }
        if(root.left != null) traversal(root.left, depth + 1);
        if(root.right != null) traversal(root.right, depth + 1);

    }
}

路径总和

题目链接/文章讲解/视频讲解
112. 路径总和,和 113. 路径总和ii 一起做了。 优先掌握递归法。

112

思路

  • 使用递归法,遍历顺序:前中后皆可,因为不涉及中节点的处理逻辑

伪代码


public boolean haspathsum(Treenode root, int targetsum){
        if (root == null) return false;
        return traversal(root, sum - root.val);
}
//这里是Boolean,有返回值是因为在二叉树中是找有没有某一条路径符合,
//所以只要找到一条符合的路径就原地返回,不一定所有的都要进行遍历
//直接传入目标值,遇到一个节点就减去,这样代码简洁一些
boolean traversal(TreeNode node, int count) {
        //终止条件:遇到叶子节点
        if (cur.left == null && cur.right == null && count == 0) return true; // 遇到叶子节点,并且计数为0,符合题目要求
        if (cur.left == null && cur.right == null) return false; // 遇到叶子节点直接返回
        //单层递归逻辑
        if (node.left != null) { // 左
            count -= node.left.val; // 递归,处理节点
            if (traversal(node.left, count)) return true;  //结果要继续向上返回,把true的结果返回给根节点,告诉找到了符合题意的路径
            count += cur.left.val; // 回溯,撤销处理结果
        }
//这里的代码可以简化为 traversal(node.left, count -= node.left.val)
// haspathsum(root.left, targetsum - root.val)
        if (node.right != null) { // 右
            count -= node.right.val; // 递归,处理节点
            if (traversal(node.right, count)) return true;
            count += node.right.val; // 回溯,撤销处理结果
        }
        return false;

}
/**
 * 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;
 *     }
 * }
 */
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

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

/**
 * 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;
 *     }
 * }
 */
class Solution {
    private List<List<Integer>> result = new ArrayList<>();
    private List<Integer> path = new ArrayList<>();

    public List<List<Integer>> pathSum(TreeNode root, int sum){
        result.clear();
        path.clear();
        if(root == null) return result;
        path.add(root.val); // 把根节点放进路径
        traversal(root, sum - root.val);
        return result;

    }
    
    private void traversal(TreeNode cur, int count){

        if(cur.left == null && cur.right == null && count == 0){
            result.add(new ArrayList<>(path));
            return;
        }
        if(cur.left == null && cur.right == null) return;
        if(cur.left != null){
            path.add(cur.left.val);
            count -= cur.left.val;
            traversal(cur.left, count);
            count += cur.left.val;
            path.remove(path.size() - 1); 
        }
        if(cur.right != null){
            path.add(cur.right.val);
            count -= cur.right.val;
            traversal(cur.right, count);
            count += cur.right.val;
            path.remove(path.size() - 1); 
        }

    }
}

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

题目链接/文章讲解/视频讲解
本题算是比较难的二叉树题目了,大家先看视频来理解。
106.从中序与后序遍历序列构造二叉树,105.从前序与中序遍历序列构造二叉树 一起做,思路一样的

106

根据一棵树的中序遍历与后序遍历构造二叉树。
注意: 你可以假设树中没有重复的元素。
例如,给出
中序遍历 inorder = [9,3,15,20,7]
后序遍历 postorder = [9,15,7,20,3]

思路

  • 后序遍历最后一个节点一定是根节点,所以中节点为3;
  • 在中序遍历中,中是3,左区间就是9,右区间就是15,20,7;
  • 再看后序里面,9就是左区间元素,右子树里才有[15,7,20],最后一个元素是20,所以20一定是中间节点
  • 中序再切割中间节点,只剩下15和7,所以15是左,7是右
  • 大致思路:后序找到最后一个,就是中间节点,用该节点来切割中序遍历,又反过来用中序切割后序。

伪代码


    private TreeNode traversal(List<Integer> inorder, List<Integer> postorder) {
//中止条件:后序数组为空
        if (postorder.size() == 0) return null;

        // 后序遍历数组最后一个元素,就是当前的中间节点
        int rootValue = postorder.get(postorder.size() - 1);
        TreeNode root = new TreeNode(rootValue);

        // 叶子节点  一步优化
        if (postorder.size() == 1) return root; //这里是下面单层递归中,节点的右孩子或者左孩子会接住返回的root

        // 找到中序遍历的切割点
        int delimiterIndex;
        for (delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex++) {
            if (inorder.get(delimiterIndex) == rootValue) break;  //找到切割的点,留下index,确定中序数组切割的下标值
        }

        // 切割中序数组  为了得到一个左中序数组和一个右中序数组
        // 左闭右开区间:[0, delimiterIndex)
        List<Integer> leftInorder = new ArrayList<>(inorder.subList(0, delimiterIndex));
        // [delimiterIndex + 1, end)
        List<Integer> rightInorder = new ArrayList<>(inorder.subList(delimiterIndex + 1, inorder.size()));

        // postorder 舍弃末尾元素
        postorder.remove(postorder.size() - 1);

        // 切割后序数组
        // 依然左闭右开,注意这里使用了左中序数组大小作为切割点
        // [0, leftInorder.size)
        List<Integer> leftPostorder = new ArrayList<>(postorder.subList(0, leftInorder.size()));
        // [leftInorder.size(), end)
        List<Integer> rightPostorder = new ArrayList<>(postorder.subList(leftInorder.size(), postorder.size()));

        root.left = traversal(leftInorder, leftPostorder);
        root.right = traversal(rightInorder, rightPostorder);

        return root;
    }

    public TreeNode buildTree(List<Integer> inorder, List<Integer> postorder) {
        if (inorder.size() == 0 || postorder.size() == 0) return null;
        return traversal(inorder, postorder);
    }

中序和前序

  • 中序:左中右,前序,中左右,用前序里确定的中去中序中分割
/**
 * 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;
 *     }
 * }
 */
class Solution {
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        if(inorder.length == 0 || postorder.length == 0) return null;
        List<Integer> inorderList = new ArrayList<>();
        for (int num : inorder) {
            inorderList.add(num);
        }
        List<Integer> postorderList = new ArrayList<>();
        for (int num : postorder) {
            postorderList.add(num);
        }
        return traversal(inorderList, postorderList);
    }
    private TreeNode traversal(List<Integer> inorder, List<Integer> postorder) {
        if(postorder.size() == 0) return null;
        int rootValue = postorder.get(postorder.size() - 1);
        TreeNode root = new TreeNode(rootValue);
        if(postorder.size() == 1) return root;
        int index;
        for(index = 0; index < inorder.size(); index++){
            if(rootValue == inorder.get(index)) break;
        }
        List<Integer> leftInorder = new ArrayList<>(inorder.subList(0, index));
        List<Integer> rightInorder = new ArrayList<>(inorder.subList(index+1, inorder.size()));

        postorder.remove(postorder.size()-1);

        List<Integer> leftPostorder =  new ArrayList<>(postorder.subList(0, leftInorder.size()));
        List<Integer> rightPostorder = new ArrayList<>(postorder.subList(leftInorder.size(), postorder.size()));

        root.left = traversal(leftInorder, leftPostorder);
        root.right = traversal(rightInorder, rightPostorder);

        return root;
    }
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,233评论 6 495
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,357评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,831评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,313评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,417评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,470评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,482评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,265评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,708评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,997评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,176评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,827评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,503评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,150评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,391评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,034评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,063评论 2 352

推荐阅读更多精彩内容