算法学习day20

news2024/11/24 20:50:30

文章目录

      • 513.找树左下角的值
        • 递归
        • 迭代
      • 112 .路径总和
        • 递归
        • 迭代
      • 113.路径总和II
        • 递归
      • 106.从中序与后序遍历序列构造二叉树
        • 递归
      • 105.从前序与中序遍历序列构造二叉树
        • 卡尔递归版本
        • 递归优化
      • 总结

513.找树左下角的值

给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。

假设二叉树中至少有一个节点。

示例 1:

img

输入: root = [2,1,3]
输出: 1

示例 2:

img

输入: [1,2,3,4,null,5,6,null,null,7]
输出: 7

提示:

  • 二叉树的节点个数的范围是 [1,104]
  • -231 <= Node.val <= 231 - 1

递归

利用最大深度,判断是否时最后一层,每次更新最大深度,不断更新左侧节点的返回值,第一个大于最大深度的一定是左下角的值

  • 入参:根节点,递归深度
  • 终止条件:无子节点,且当前深度大于最大深度
  • 循环逻辑:迭代一层回溯一次
 class Solution {
    int maxDepth ,res ;
    public int findBottomLeftValue(TreeNode root) {
        maxDepth(root,1);
        return res ;
    }
    public void maxDepth(TreeNode root,int depth) {
        //终止条件,无子节点
        if(root.left == null && root.right == null) {
            //只有大于最大深度时才能更新最大深度,此时左右深度相同,第一个值就是左子树的值
            if(depth > maxDepth) {
                maxDepth = depth ;
                res = root.val ;
                return;
            }
        }
        //单层循环逻辑
        if(root.left!= null) {
            depth++;
            maxDepth(root.left,depth+1);
            depth--;
        }
        if(root.right!= null) {
            depth++;
            maxDepth(root.right,depth+1);
            depth--;
        }
    }

}

迭代

  • 层序遍历,每层的第一个值
class Solution {
    public int findBottomLeftValue(TreeNode root) {

        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        int res = root.val;
        while (!queue.isEmpty()) {
            int size = queue.size();
            for (int i = 0; i < size; i++) {
                TreeNode node = queue.poll();
                //队列的第一个节点是左节点的值
                if(i == 0){
                    res = node.val;
                }

                if (node.left!= null) {
                    queue.offer(node.left);
                }
                if (node.right!= null) {
                    queue.offer(node.right);
                }
            }
        }
        return res;
    }

}

112 .路径总和

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false

叶子节点 是指没有子节点的节点。

示例 1:

img

输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true
解释:等于目标和的根节点到叶节点路径如上图所示。

示例 2:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9ONVHVUC-1686571624313)(null)]

输入:root = [1,2,3], targetSum = 5
输出:false
解释:树中存在两条根节点到叶子节点的路径:
(1 --> 2): 和为 3
(1 --> 3): 和为 4
不存在 sum = 5 的根节点到叶子节点的路径。

示例 3:

输入:root = [], targetSum = 0
输出:false
解释:由于树是空的,所以不存在根节点到叶子节点的路径。

提示:

  • 树中节点的数目在范围 [0, 5000]
  • -1000 <= Node.val <= 1000
  • -1000 <= targetSum <= 1000

递归

  • 递归三部曲:入参和返回值(局部变量),终止条件,单层循环逻辑
  • 入参:节点,路径、返回结果
  • 终止条件:左右子节点为空,添加返回值
  • 循环条件:每循环一层,递归+回溯
class Solution {
    int sum = 0;
    public boolean hasPathSum(TreeNode root, int targetSum) {
        if(root == null) return false;
        sum += root.val;
        if(root.left == null && root.right == null) {
            return sum == targetSum;
        }
        if(root.left!= null){
            if(hasPathSum(root.left, targetSum)){
                return true;
            }
            sum -= root.left.val;
        }

        if(root.right!= null){
            if(hasPathSum(root.right, targetSum)){
                return true;
            }
            sum -= root.right.val;
        }
        return false;
    }
}

迭代

  • 参考二叉树的层序先序,借助栈+回溯,类似于递归

113.路径总和II

给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。

叶子节点 是指没有子节点的节点。

示例 1:

img

输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:[[5,4,11,2],[5,8,4,5]]

示例 2:

img

输入:root = [1,2,3], targetSum = 5
输出:[]

示例 3:

输入:root = [1,2], targetSum = 0
输出:[]

提示:

  • 树中节点总数在范围 [0, 5000]
  • -1000 <= Node.val <= 1000
  • -1000 <= targetSum <= 1000

递归

  • 入参:每个节点,目标值,返回结果,路径
  • 终止条件:sum == targetSum
  • 循环逻辑:每次回溯的节点,为当前递归的那个节点(递归参数里的节点)
class Solution {
    int sum = 0 ;
    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        List<List<Integer>> res = new ArrayList<>();
        if (root == null) {
            return res;
        }
        List<Integer> paths = new ArrayList<>();
        dfs(root, targetSum, res, paths);
        return res;
    }

    public void dfs(TreeNode root,int targetSum , List<List<Integer>> res,  List<Integer> paths){
        //终止条件
        if (root == null) {
            return;
        }
        sum += root.val;
        paths.add(root.val);

        if (root.left == null && root.right == null) {
            if (sum == targetSum) {
                res.add(new ArrayList<>(paths));
            }
            return;
        }
        if(root.left != null){
            dfs(root.left,targetSum, res, paths);
            sum -= root.left.val;
            paths.remove(paths.size() - 1);
        }
        if(root.right!= null){
            dfs(root.right,targetSum, res, paths);
            sum -= root.right.val;
            paths.remove(paths.size() - 1);
        }
    }
}

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

给定两个整数数组 inorderpostorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树

示例 1:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HxgyjBu1-1686571624948)(null)]

输入:inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]
输出:[3,9,20,null,null,15,7]

示例 2:

输入:inorder = [-1], postorder = [-1]
输出:[-1]

递归

class Solution {
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        //终止条件1
        if (postorder.length == 0 || inorder.length == 0) {
            return null;
        }
        //创建根节点
        TreeNode root = new TreeNode(postorder[postorder.length - 1]);
        //终止条件2
        if (postorder.length == 1) {
            return root;
        }
        //通过中序找到切割点index
        int index = 0 ;
        for (int i = 0; i < inorder.length; i++) {
            if(inorder[i] == root.val) {
                index = i ;
                break;
            }
        }
        //切中序数组  始终左闭右开
        int[] leftInOrderSlice =subArray(inorder,0,index);

        int[] rightInOrderSlice =subArray(inorder,index+1,inorder.length);

        //切后序数组  以中序数组的左半部分的数组长度,切先序数组的左半部分
        int[] leftPreSlice = subArray(postorder,0,leftInOrderSlice.length);

        //切后序数组  以中序数组的右半部分的数组长度,切先序数组的右半部分
        int[] rightPreSlice = subArray(postorder,leftPreSlice.length,leftPreSlice.length+rightInOrderSlice.length);

        //创建左子树
        root.left = buildTree(leftInOrderSlice,leftPreSlice);
        //创建右子树
        root.right = buildTree(rightInOrderSlice,rightPreSlice);
        return root;
    }
    public int[] subArray(int[] array, int start, int end){
        if(start>=end){
            return new int[0];
        }
        int[] result = new int[end-start];
        for (int i = start; i < end; i++) {
            result[i-start] = array[i];
        }
        return result;
    }
}

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

给定两个整数数组 preorderinorder ,其中 preorder 是二叉树的先序遍历inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。

示例 1:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OBei0zLm-1686571624293)(null)]

输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]

示例 2:

输入: preorder = [-1], inorder = [-1]
输出: [-1]

提示:

  • 1 <= preorder.length <= 3000
  • inorder.length == preorder.length
  • -3000 <= preorder[i], inorder[i] <= 3000
  • preorderinorder无重复 元素
  • inorder 均出现在 preorder
  • preorder 保证 为二叉树的前序遍历序列
  • inorder 保证 为二叉树的中序遍历序列

卡尔递归版本

  • 留个步骤
    • 终止条件,任意数组为空
    • 前序数组为1,结束遍历,返回根
    • 使用前序数组的第一个节点,切割中序数组
    • 分割中序数组的左右两部分
    • 根据中序数组的左边部分的长度,分割前序数组,得到线序数组的左半部分和右边部分
    • 递归给左右字数赋值
class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        //终止条件1
        if (preorder.length == 0 || inorder.length == 0) {
            return null;
        }
        //创建根节点
        TreeNode root = new TreeNode(preorder[0]);
        //终止条件2
        if (preorder.length == 1) {
            return root;
        }
        //通过中序找到切割点index
        int index = 0 ;
        for (int i = 0; i < inorder.length; i++) {
            if(inorder[i] == root.val) {
                index = i ;
                break;
            }
        }
        //切中序数组  始终左闭右开
        int[] leftInOrderSlice =subArray(inorder,0,index);

        int[] rightInOrderSlice =subArray(inorder,index+1,inorder.length);

        //切前序数组  以中序数组的左半部分的数组长度,切先序数组的左半部分
        int[] leftPreSlice = subArray(preorder,1,leftInOrderSlice.length+1);

        //切后序数组  以中序数组的右半部分的数组长度,切先序数组的右半部分
        int[] rightPreSlice = subArray(preorder,leftPreSlice.length+1,preorder.length);

        //创建左子树
        root.left = buildTree(leftPreSlice,leftInOrderSlice);
        //创建右子树
        root.right = buildTree(rightPreSlice,rightInOrderSlice);
        return root;
    }
    public int[] subArray(int[] array, int start, int end){
        if(start>=end){
            return new int[0];
        }
        int[] result = new int[end-start];
        for (int i = start; i < end; i++) {
            result[i-start] = array[i];
        }
        return result;
    }
}

递归优化

  • 每次都是从原始数组的部分进行递归,可通过开始和结束下标,避免分割数组创建的临时数组,节省空间
class Solution {
    Map<Integer, Integer> map = new HashMap<>();
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        // 用map保存中序序列的数值对应位置
        for (int i = 0; i < inorder.length; i++) { 
            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;
    }
}

总结

  • 递归三部曲:参数和返回值、终止条件、单层循环逻辑
  • 回溯要记录路径,一般用List记录所经过的path,回溯要和递归一起存在
  • 先序+中序,后序+中序遍历的数组,可还原唯一一颗二叉树,先序+后序则无法确认左右顺序,无法还原唯一
  • 还原二叉树,第一步找到根节点,在通过根节点切割中序、先序/后续,要梳理清楚切割的开始和结束位置

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/639213.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

K8S从入门到精通之基本组件介绍

文章目录 0.前言k8s 的dashboard基本组件活动图 1. 基本概念1.1. kube-apiserver1.2. etcd1.3. kube-scheduler1.4. kube-controller-manager1.5. kubelet1.6. kube-proxy1.7. coredns&#xff1a;1.8. Container Runtime1.9. Ingress Controller1.10. Storage Plugin1.11. Das…

原点安全携“金融机构消费者个人信息保护解决方案”亮相 2023 中国金融数字化转型发展大会

6 月 7 日&#xff0c;由中国金融电子化集团有限公司、南京市建邺区人民政府、中国人民银行南京分行主办&#xff0c;主题为“数驱转型 智创未来”的「2023 中国金融数字化转型发展大会暨第十三届中国城市商业银行信息化发展创新座谈会」于南京国际博览中心隆重召开。 本次会议…

使用POI实现JAVA操作Excel

Apache POI POI提供API给JAVA程序对Microsoft Office格式档案读和写的功能 POI工具介绍 POI 是用Java编写的免费开源的跨平台的 Java API&#xff0c;Apache POI提供API给Java程式对Microsoft Office格式档案读和写的功能。主要是运用其中读取和输出excel的功能。 POI官网地…

每日一练 | 华为认证真题练习Day57

1、两台路由器通过PPP链路互连&#xff0c;管理员在两台路由器上配置了OSPF&#xff0c;且运行在同一个区域中&#xff0c;如果它们的Router ID相同&#xff0c;则下面描述正确的&#xff08;&#xff09;。 A. 两台路由器将会建立正常的完全邻居关系 B. 两台路由器将不会互相…

看看苹果如何平衡Vision Pro性能和功耗

众所周知&#xff0c;Quest 2极为严格的控制SoC运行频率&#xff0c;目的就是保证整机的“散热性能”&#xff0c;说白了就是发热不能严重、风扇噪音不能大。这也是VR头戴设备中降低用户体验的两个关键指标。 对于Quest 2很直接的一个优势就是&#xff0c;用户大多时候听不到狂…

Javaweb学习路线(3)——SpringBoot入门、HTTP协议与Tomcat服务器

一、SpringBoot入门 &#xff08;一&#xff09;第一个Springboot案例 1、创建Springboot工程&#xff0c;添加依赖。 2、定义类&#xff0c;添加方法并添加注释 3、运行测试。 pom.xml&#xff08;框架自动生成&#xff09; <?xml version"1.0" encoding&quo…

linux上安装es、Kibana、ik分词

基本概念 ES是什么&#xff1f;我们可以把ES比作一个Mysql数据库&#xff0c;同样用来存储数据&#xff0c;不过比Mysql提供了更多的搜索功能,例如分词搜索&#xff0c;关联度搜索等&#xff0c;而且搜索速度也不是同一级别的&#xff0c; ES能够实现百万数据/秒的查询速度。接…

【Java|golang】1171. 从链表中删去总和值为零的连续节点

给你一个链表的头节点 head&#xff0c;请你编写代码&#xff0c;反复删去链表中由 总和 值为 0 的连续节点组成的序列&#xff0c;直到不存在这样的序列为止。 删除完毕后&#xff0c;请你返回最终结果链表的头节点。 你可以返回任何满足题目要求的答案。 &#xff08;注意…

Linux内核中内存管理相关配置项的详细解析9

接前一篇文章&#xff1a;Linux内核中内存管理相关配置项的详细解析8 十三、Enable recovery from hardware memory errors 对应配置变量为&#xff1a;CONFIG_MEMORY_FAILURE。 此项只有选中和不选中两种状态&#xff0c;默认为选中。 内核源码详细解释为&#xff1a; Enab…

HTML5 input元素新的特性

在HTML5中&#xff0c;<input>元素增加了许多新的属性、方法及控件。本文章分别对这三方面进行介绍。 目录 1. 属性 2. 方法 3. 新控件 1. 属性 <input>元素在HTML5中新增加的属性有&#xff1a;autocomplete 、autofocus、form、formaction、formenctype、f…

人工智能网站KameAI

人工智能网站KameAI 前言 人工智能的崛起与发展随着科技的飞速发展&#xff0c;人工智能(AI)已经逐渐成为我们生活中不可或缺的一部分。它的出现不仅改变了我们与世界的互动方式&#xff0c;还为各行各业带来巨大的便利。今天&#xff0c;我们就来聊一聊一个人工智能网站—Ka…

合并两个排序的链表

题目&#xff1a; 输入两个递增排序的链表&#xff0c;合并着两个链表并使新链表中的结点仍然是按照递增顺序的。例如输入的链表1和链表2如下&#xff0c;合并后的为链表3。链表的结点定义如下&#xff1a; struct ListNode {int value;ListNode *next; }; 复制 解题思路&…

APP性能测试,你需要关注哪些指标?

一、Android客户端性能测试常见指标 1、内存 2、CPU 3、流量 4、电量 5、启动速度 6、滑动速度、界面切换速度 7、与服务器交互的网络速度 二、预期标准指定原则 1、分析竞争对手的产品&#xff0c;所有指标要强于竞品 2、产品经理给出的预期性能指标数据 3、符合业…

Unity3D:Scene 视图导航

推荐&#xff1a;将 NSDT场景编辑器 加入你的3D工具链 3D工具集&#xff1a; NSDT简石数字孪生 Scene 视图导航 场景视图具有一组导航控件&#xff0c;可帮助您高效地四处移动&#xff1a; 场景视图辅助图标移动、旋转和缩放工具居中工具 场景视图辅助图标 场景辅助图标将显…

MySQL性能优化:索引优化

一、索引基础知识 &#xff08;一&#xff09;辅助索引/二级索引 叶子节点除了包含键值以外&#xff0c;每个叶子节点中的索引行中还包含了一个书签( bookmark) &#xff08;每个索引一颗B树&#xff0c;不包含行记录的全部数据&#xff09; &#xff08;二&#xff09; 回表…

docker-harbor私有仓库部署

什么是Harbor Harbor 是 VMware 公司开源的企业级 Docker Registry 项目&#xff0c;其目标是帮助用户迅速搭建一个企业级的 Docker Registry 服务。 Harbor以 Docker 公司开源的 Registry 为基础&#xff0c;提供了图形管理 UI 、基于角色的访问控制(Role Based AccessContr…

HTML5 语义元素(一)页面结构

本篇主要介绍HTML5增加的语义元素中关于页面结构方面的&#xff0c;包含&#xff1a; <article>、<aside>、<figure>、<figcaption>、<footer>、<header>、<main>、<nav>、<section>等元素。 目录 1. 语义元素介绍 1.…

01-SPA单页面的理解,它的优缺点分别是什么?如何实现SPA应用(了解))

文章目录 什么是SPA二、SPA和MPA的区别实现一个SPA1.引入库2.读入数据 总结 什么是SPA (single-page application),是一种网络应用程序或网站的模型&#xff0c;它通过动态重写当前页面来与用户交互&#xff0c;这种方法避免了页面之间切换打断用户体验,在单页应用中&#xff…