java算法day11

news2024/11/15 10:55:44
  • 二叉树的递归遍历
  • 二叉树的非递归遍历写法
  • 层序遍历

递归怎么写?

按照三要素可以保证写出正确的递归算法:

1.确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。

2.确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。

3.确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。


前置必会

一定要会自己写出树的类定义

public class TreeNode{
	TreeNode left;
	TreeNode right;
	int val;
	TreeNode(){}
	TreeNode(int val){this.val = val}
	TreeNode(int val,TreeNode right,TreeNode left){
	this.val = val;
	this.left = left;
	this.right = right;
}

144.二叉树的前序遍历

递归写法

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        dfs(root,result);
        return result;
    }

    void dfs(TreeNode root,List<Integer> result){
        if(root==null){
            return;
        }
        result.add(root.val);
        dfs(root.left,result);
        dfs(root.right,result);
    }
    
}

非递归写法:
我认为就是记思路。
1.用一个栈作为辅助。
2.前序遍历是根左右,每次先处理的是中间节点,那么先将根节点放入栈中,然后将右孩子加入栈,再加入左孩子。之所以是反过来是因为,这样出栈的时候才是根左右的顺序。直接来看图模拟
请添加图片描述
即每次在处理根节点的时候,将根节点出队,然后将其右孩子先进栈,再将左孩子进栈。
这样进行处理之后,出栈的结果就会是前序遍历的结果。

如果还是不懂,我建议直接结合代码,然后结合上面图,记下来这个做法。我觉得这个直接想是不好想的。

非递归其实就是用辅助数据结构,配合循环

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
    //结果集
        List<Integer> result = new ArrayList<>();
        //特判
        if(root == null){
            return result;
        }
        //创建辅助栈
        Deque<TreeNode> st = new ArrayDeque<>();
        //根节点先入栈
        st.push(root);
        //当栈不空的时候
        while(!st.isEmpty()){
        //先处理根节点,即将根节点出栈。这就对应着根
            TreeNode node = st.pop();
            //将出栈的根节点加入结果集
            result.add(node.val);
            //先将右节点加入栈中,可以这么想,早点加入,那么就晚点出。所以右节点出的时候,要比左节点晚,那么这里出也就是和上面节点出栈一个道理。所以这里就完成了根左右里面的根左。因为左节点进的晚,出的就早。
            if(node.right!=null){
                st.push(node.right);
            }
            //然后才是到左节点,晚点进就可以早点出。
            if(node.left!=null){
                st.push(node.left);
            }
        }
        
        return result;
    }
}

这是我第二写总结的流程:
1.初始化
创建一个空的结果列表
创建一个辅助栈
将根节点压入栈中

2.主循环: 当栈不为空时,执行以下操作:

a. 处理当前节点:
从栈中弹出一个节点
将该节点的值添加到结果列表中

b. 压入子节点:
如果该节点有右子节点,将右子节点压入栈中
如果该节点有左子节点,将左子节点压入栈中
返回结果列表

这种方法的关键点在于
根节点最先被处理,这符合前序遍历的"根-左-右"顺序。
右子节点先于左子节点入栈,这确保了左子节点会先于右子节点被处理。
这种非递归方法的优点是:

避免了递归调用的开销
对于深度很大的树,可以防止栈溢出
实现简单,易于理解
与中序遍历的非递归方法相比,前序遍历的非递归实现更为直观,因为它可以直接按照遍历顺序处理节点,而不需要像中序遍历那样先到达最左节点。

145.二叉树的后序遍历

递归写法

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result  = new ArrayList<>();
        dfs(root,result);
        return result;
    }

    public void dfs(TreeNode root,List<Integer> result){
        if(root==null){
            return;
        }
        dfs(root.left,result);
        dfs(root.right,result);
        result.add(root.val);
    }
}

非递归写法:
这个解法是一个技巧解。
由于前序遍历是根左右,而后续遍历是左右根。所以如果调整一下前序遍历的顺序,先加左节点,再加右节点,那么得到的结果就是按根右左规则得到的。所以此时做翻转,那么得到的结果就是按左右根得到的结果。与后序遍历的结果不谋而合。
所以解法就是线序遍历调整左右入栈方式,然后再对最终结果做翻转。

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        Deque<TreeNode> st = new ArrayDeque<>();
        List<Integer> result = new ArrayList<>();
        if(root==null){
            return result;
        }
        st.push(root);
        while(!st.isEmpty()){
            TreeNode node = st.pop();
            result.add(node.val);
            if(node.left!=null){
                st.push(node.left);
            }
            if(node.right!=null){
                st.push(node.right);
            }
        }
        Collections.reverse(result);
        return result;
    }
}

94.二叉树的中序遍历

递归写法

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        dfs(root,result);
        return result;
    }

    public void dfs(TreeNode root,List<Integer> result){
        if(root==null){
            return;
        }
        dfs(root.left,result);
        result.add(root.val);
        dfs(root.right,result);
    }
}

非递归写法:
看这个图,然后结合图片,记下这个做法:
请添加图片描述
1.初始化
创建一个空的结果列表和一个空的栈
将当前节点设置为根节点

2.主循环: 当当前节点不为空或栈不为空时,执行以下操作:

a. 左子树遍历
如果当前节点不为空
将当前节点压入栈
移动到左子节点

b. 节点处理
如果当前节点为空(即已经到达最左端)
从栈中弹出一个节点
将该节点的值添加到结果列表中
将当前节点移动到右子节点

3.返回结果列表

这种方法模拟了递归的过程:

首先尽可能深入地遍历左子树
然后处理节点本身
最后遍历右子树
使用栈来保存已经遍历过的父节点,以便在处理完左子树后能够回溯。

还是按代码思维走一遍,又和自己想的还是有一点区别。

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
    //结果集
        List<Integer> result = new ArrayList<>();
        //特判
        if(root==null){
            return result;
        }
        //辅助栈
        Deque<TreeNode> st = new ArrayDeque<>();
        //用一个遍历指针指向root
        TreeNode cur = root;
        //这里就是进递归处理的逻辑,循环条件决定了能不能递归处理下去。
        //cur!=null表明这个元素不空,可以进栈,!st.isEmpty(),是用来回溯的,表明栈中还有走过的元素需要进行处理。所以栈中走过的元素没处理完也要继续处理。
        while(cur!=null || !st.isEmpty()){
        //根据中序,左根右的走法,需要一路向最左下走。走的过程就一直入栈,保存之前的状态。如果cur==null,说明走到最左下的节点的左分支上了,也就是null。
            if(cur!=null){
                st.push(cur);
                cur = cur.left;
            }else{
            //不能继续往左走了才处理这里,这里就是开始回溯,回溯一下回到最左下的节点,此节点并不是null。那就把这个元素出栈,把这个元素的val加入结果集。由于每个元素都要左根右,这里处理了根节点,那还要往右节点走。下一波显然cur还是null,那么此时就会弹出沿着路线返回的根节点,往后都是这样。注意是依靠弹出的节点进行转移,因为栈里面的节点才是记录先前的状态,别自己瞎写一个。
                cur = st.pop();
                result.add(cur.val);
                cur = cur.right;
            }
        }
        //当st里面为空的时候,就是所有节点都处理完的时候。
        return result;
    }
}

102.二叉树的层序遍历

用队列。
看一个图就懂了。请添加图片描述
队列先进先出,符合一层一层遍历的逻辑。
下面的代码就是二叉树层序遍历的模板题,会这个模板,之后遇到只要是层序遍历的题,随便杀。

总的来说,这个解法看完,里面的len的处理我是当时没想到的。

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();
        checkFun(root,result);
        return result;
    }

    public void checkFun(TreeNode node,List<List<Integer>> result){
    //特判
        if(node==null){
            return;
        }
        //辅助队列
        Deque<TreeNode> que = new ArrayDeque<>();
        //根节点先入队
        que.offerLast(node);
		//队列不空就代表流程还没有结束
        while(!que.isEmpty()){
        //记录一层元素的结果集
            List<Integer> itemList = new ArrayList<Integer>();
            //在一开始先记录长度,该长度即队列目前的长度,就是该层元素的个数。
            //记录len就是为了做一个界限,即处理完这一层元素后,就要收集一次结果集。
            int len = que.size();
            
            //这个循环做依次将该层元素出队,并扩展其左右节点加入队列当中。
            while(len>0){
            //先出队
                TreeNode tmpNode = que.pollFirst();
                //加入结果集
                itemList.add(tmpNode.val);
				//扩展左右节点,加入队列中。
                if(tmpNode.left!=null){
                    que.offerLast(tmpNode.left);
                }
                if(tmpNode.right!=null){
                    que.offerLast(tmpNode.right);
                }
                //做完之后该层元素数量要-1
                len--;
            }
            //处理完一层后记录一次结果。
            result.add(itemList);

        }
    }
}

107.二叉树的层序遍历Ⅱ

在上题的基础上将结果集翻转就结束了。

class Solution {
    public List<List<Integer>> levelOrderBottom(TreeNode root) {
        List<List<Integer>> result = new ArrayList<>();
        checkFun(root,result);
        Collections.reverse(result);
        return result;
    }

    public void checkFun(TreeNode node,List<List<Integer>> result){
        if(node==null){
            return;
        }
        Deque<TreeNode> que = new ArrayDeque<>();
        que.offerLast(node);

        while(!que.isEmpty()){
            List<Integer> itemList = new ArrayList<Integer>();
            int len = que.size();
            
            while(len>0){
                TreeNode tmpNode = que.pollFirst();
                itemList.add(tmpNode.val);

                if(tmpNode.left!=null){
                    que.offerLast(tmpNode.left);
                }
                if(tmpNode.right!=null){
                    que.offerLast(tmpNode.right);
                }
                len--;
            }
            result.add(itemList);

        }
    }
}

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

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

相关文章

运维锅总详解进程、内核线程、用户态线程和协程

I/O 密集型应用、计算密集型应用应该用什么实现&#xff1f;进程、内核线程、用户态线程、协程它们的原理和应用场景又是什么&#xff1f;如何组合它们才能让机器性能达到最优&#xff1f;它们的死锁和竞态又是什么&#xff1f;如何清晰地表示它们之间的关系&#xff1f;希望读…

创新设计策略:提升大屏幕可视化设计效果的关键方法

随着科技的不断发展和数据量的快速增长&#xff0c;数据可视化大屏在各个行业中的应用越来越广泛&#xff0c;可以帮助人们更好地理解和分析数据&#xff0c;可视化大屏设计也因此成了众多企业的需求。但很多设计师对可视化大屏设计并不了解&#xff0c;也不知道如何制作可视化…

一.9 重要主题

在此&#xff0c;小结一下我们旋风式的系统漫游。这次讨论得出一个很重要的观点&#xff0c;那就是系统不仅仅只是硬件。系统是硬件和系统软件互相交织的集合体。它们必须共同协作以达到运行应用程序的最终目的。本书的余下部分会讲述硬件和软件的详细内容&#xff0c;通过了解…

UnityHub 无法添加模块问题

文章目录 1.问题描述2.问题解决 1.问题描述 在Hub中无法添加模块 2.问题解决 1、点击设置 2、设置版本安装位置 可以发现installs的安装位置路径设置不是unity安装位置&#xff0c;这里我们更改成自己电脑unity安装位置的上一级路径 添加模块正常&#xff1a;

java链表常见简单面试算法题

头插法、尾插法 头插法&#xff1a;先待插入指向头结点的next&#xff0c;后头结点的next指向待插入。 尾插法&#xff1a;借助尾指针&#xff0c;直接插入 /*** 头插法* param head* return*/public static Node head_insert(Node head, int t){Node nodenew Node(t);node.set…

vitis2021.1生成设备树

PL端功能相关的dtsi动态设备树源文件的生成&#xff0c;需依赖Xilinx设备树源码包 其下载地址为&#xff1a;https://github.com/Xilinx/device-tree-xlnx/tree/xlnx_rel_v2021.1 打开vitis软件&#xff0c;导入xilinx设备树源码包 点击Xilinx->Software Repositories 完成…

中霖教育:经济师的十个专业类别怎么选?

经济师一共包含十个专业类别&#xff0c;分别是工商管理、农业经济、财政税收、金融、保险、人力资源管理、旅游经济、运输经济、建筑与房地产经济、知识产权。 经济师选择报考专业时有哪些建议? 1、职业规划是选择专业的首要考虑点。未来的职业发展途径应与所选专业紧密相连…

使用lv虚拟卷扩展磁盘

使用centos演示。 首先创建centos虚拟机。链接&#xff1a;VMWARE安装Centos8,并且使用ssh连接虚拟机-CSDN博客 1. 增加磁盘。 选中要扩容的虚拟机&#xff0c;右键选择设置&#xff0c;然后点击磁盘&#xff0c;选择添加。 这里选择NVM的磁盘。选择这种磁盘是为了保持与之前…

昨日头条管理系统设计

设计一个“昨日头条”类似的内容管理系统时&#xff0c;我们可以借鉴内容管理系统设计原则&#xff0c;并针对“昨日头条”这类新闻资讯类应用的特点进行定制化设计。以下是一些关键点&#xff1a; 1. 内容采集与整合 智能抓取&#xff1a;设计爬虫系统自动抓取国内外各大新闻…

SOLIDWORKS 2024多方面优势

在工程设计领域&#xff0c;SOLIDWORKS始终以其优越的功能和不断创新的技术&#xff0c;带领着行业的发展方向。随着SOLIDWORKS 2024版本的发布&#xff0c;这款三维设计软件再次展现了其多方面的显著优势&#xff0c;为设计师和工程师们提供了更加智能、便捷的工作平台。 一、…

单词间隔重复算法

间隔重复算法 理论背景 遗忘曲线是一种描述记忆遗忘率的模型&#xff0c;艾宾浩斯在其著作《记忆&#xff1a;实验心理学的贡献》中首次详细描述了遗忘曲线&#xff0c;他使用了一些无意义的字母组合作为记忆对象&#xff0c;通过在不同的时间间隔后检查记忆的遗忘程度&#…

论文学习_Getafix: learning to fix bugs automatically

1. 引言 研究背景:现代生产代码库极其复杂并且不断更新。静态分析器可以帮助开发人员发现代码中的潜在问题(在本文的其余部分中称为错误),这对于在这些大型代码库中保持高代码质量是必要的。虽然通过静态分析尽早发现错误是有帮助的,但修复这些错误的问题在实践中仍然主要…

浅谈化工厂环保管理的痛点、智慧环保的必要性及EHS系统的实现路径

在全球环保意识日益增强的背景下&#xff0c;化工厂作为工业领域的重要组成部分&#xff0c;其环保管理显得尤为重要。然而&#xff0c;化工厂在追求经济效益的同时&#xff0c;也面临着诸多环保管理的痛点。本文将围绕化工厂环保管理的痛点、化工厂为何需要智慧环保以及如何借…

一阶线性微分方程应用实例:并联RC电路恒定电流求解电压

对于并联RC电路&#xff0c;我们可以通过求解微分方程来找出电压 V(t)。 微分方程求解 我们开始于给定的表达式&#xff1a; 重写方程&#xff1a; 将方程的形式调整为标准的线性微分方程形式&#xff1a; 这是一个一阶线性微分方程&#xff0c;我们可以使用积分因子法来解…

ROS服务通信自定义srv

服务通信自定义srv 流程:创建ROS功能包按照固定格式创建srv文件编译配置文件编译生成中间文件 流程: srv 文件内的可用数据类型与 msg 文件一致&#xff0c;且定义 srv 实现流程与自定义 msg 实现流程类似&#xff0c;需查阅msg文件的可以浏览ROS话题通信流程自定义数据msg格式…

红日靶场----(三)2.漏洞利用

上期的通过一句话木马实现对目标主机的持久后门 我使用的是蚁剑&#xff0c;蚁剑安装及使用参考&#xff1a; 下载地址&#xff1a; GitHub - AntSwordProject/AntSword-Loader: AntSword 加载器 安装即使用&#xff1a; 1. 快速入门 语雀 通过YXCMS的后台GETSHELL 利用…

【测开能力提升-fastapi框架】介绍简单使用

0. 前期说明 立了很多flag(开了很多专题)&#xff0c;但坚持下来的没几个。也干了很多测试工作(起初是硬件(Acoustic方向)测试 - 业务功能测试 - 接口测试 - 平台功能测试 - 数据库测试 - py自动化测试 - 性能测试 - 嵌入式测试 - 到最后的python测试开发)&#xff0c;最终还是…

链表---头插法+尾插法

本博客介绍了单链表的实现&#xff0c;以及头插法尾插法的代码实现 1.定义一个结点类 class ListNode{int value;ListNode next;public ListNode(int value) {super();this.value value;}Overridepublic String toString() {return "ListNode{" "value" …

Android 自定义Edittext 和TextView 提示文字和填入内容不同的粗细组件

近期项目中又EditText 以及TextView 这两个组件需要用到提示文字 以及 填入文字要保持不同的粗细程度,所以记录一下 首先 是EditText 组件的自定义 BLEditText 继承的这个组件是一个三方的组件,可以在很大程度上减少drawable的编写,有兴趣的可以去相关的Git去看一下 点击查看,…

excel有条件提取单元格特定文本(筛选纯文字的单元格或含有数字的单元格、单元格提取不同的文本长度)

实际工作背景 需要对导出的银行流水中的数十个村以及对应的村小组进行分组统计&#xff0c;但是初始的表格中村和小组是混在一起的&#xff0c;如下图所示&#xff1a; 目的&#xff1a;将大树村和大树村小组名称分别筛选出来 1.观察发现&#xff0c;大树村小组的单元格第4…