112. 路径总和详解!!三种解法,总有一款适合你(Java)

513.找树左下角的值

题目链接:513. 找树左下角的值

BFS(迭代)法:

/**
 * 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 int findBottomLeftValue(TreeNode root) {
        // 1. BFS法
        int result = 0;
        Queue<TreeNode> queue = new ArrayDeque<>();
        queue.add(root);
        while(!queue.isEmpty()) {
            int len = queue.size();
            result = queue.peek().val;  // 保存每一层的第一个元素(即对首元素)的值
            // 开始层序遍历
            while(len > 0) {
                TreeNode temp = queue.poll();
                if(temp.left != null) {
                    queue.add(temp.left);
                }
                if(temp.right != null) {
                    queue.add(temp.right);
                }
                len--;
            }
        }
        return result;
    }
}

DFS(递归法)有点难,过段时间再补上吧

112. 路径总和 

题目链接:112. 路径总和

DFS+哈希法:

这道题,和257. 二叉树的所有路径有点像,可以说是拓展题了,我也写过题解(点击跳转)。这道题,函数参数我设计的是每个节点上的值的集合List<Integer> pathVal,和所有路径和的哈希表Set<Integer> pathValSumSet,pathVal用来记录,辅助回溯的,而哈希表用于最终查找目标值的和。

代码:

/**
 * 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) {
        // DFS(前序遍历 + 哈希法)
    	if(root == null) {
    		return false;
    	}
        Set<Integer> pathValSumSet = new HashSet<>();		// 保存每一条路径上的和
        List<Integer> pathVal = new ArrayList<>();	// 保存路径上节点的值,因为要回溯,所以函数参数应该也有路径上值的集合
        getAllPathSum(root, pathVal, pathValSumSet);
        if(pathValSumSet.contains(targetSum)) {			// 在哈希表中查找目标和是否存在二叉树所有路径和中
        	return true;
        }else {
        	return false;
        }
        
    }
    public void getAllPathSum(TreeNode node, List<Integer> pathVal, Set<Integer> pathValSumSet) {
    	pathVal.add(node.val);
    	if(node.left == null && node.right == null) {	// 如果遇到叶子结点,说明要记录这条路径的和了
    		int sum = 0;
    		for(int i = 0; i < pathVal.size(); i++) {   // 求路径总和
    			sum += pathVal.get(i);
    		}
    		// 把一跳路径上的和保存到pathValSumSet表中
    		pathValSumSet.add(sum);
    		return;     // 循环终止条件就是到叶子节点就return就行了
    	}
    	if(node.left != null) { // 必须加上,因为可能出现有左孩子(右孩子)没有右孩子(左孩子),如示例1右下角边4这个结点,只有右孩子而没有左孩子,不加这句话会报空指针异常
    		getAllPathSum(node.left, pathVal, pathValSumSet);
    		// 出了上面这个函数,说明一定是走到叶子结点了,因为是前序遍历,已经经历了上一个函数对这条路径的求和
    		// 所以需要“回溯”,即把当前的pathVal集合里存放的最后一个元素(已经求过和的叶子结点的值)删除,这样再下一次往右孩子走时,计算的是另一条路径的和了
    		// 例如,走出上面的递归,此时pathVal里的元素是示例1里的[5, 4, 11, 7]
    		// 下面将会递归进入node.right(11这个结点的右孩子),此时如果不删除pathVal里的最后一个元素,那么进入求和则会导致结果出错
    		// 因为右孩子的路径总和应该是:[5, 4, 11, 2]
    		// 所以,以上便是回溯的原因!
    		pathVal.remove(pathVal.size() - 1);
    	}
    	if(node.right != null) {
    		getAllPathSum(node.right, pathVal, pathValSumSet);
    		pathVal.remove(pathVal.size() - 1);
    	}
    	
    }
}

本题用到了回溯,如下面这部分代码:

if(node.left != null) { 
    		getAllPathSum(node.left, pathVal, pathValSumSet);
    		pathVal.remove(pathVal.size() - 1); // 回溯 
}

回溯解读:

出了getAllPathSum(node.left, pathVal, pathValSumSet);这个函数,说明一定是走到叶子结点了,因为是前序遍历,已经经历了上一个函数对这条路径的求和所以需要“回溯”,所以接下来需要把当前的pathVal集合里存放的最后一个元素(已经求过和的叶子结点的值)删除,这样再下一次往右孩子走时,计算的是另一条路径的和了

例如,出了上面的getAllPathSum之后,此时pathVal里的元素是示例1里的[5, 4, 11, 7]

下面将会递归进入node.right(11这个结点的右孩子),此时如果不删除pathVal里的最后一个元素,那么进入求和则会导致结果出错

因为右孩子的路径总和应该是:[5, 4, 11, 2],所以,需要把pathVal里的最后一个元素删除(对应代码:pathVal.remove(pathVal.size() - 1);),这就叫:回溯!

 DFS+Lst集合法:

当然,也可以不用哈希表,把递归的返回值和参数换一下,把哈希表直接换成targetSum,遇到对的的目标值一路返回true即可。

代码:

class Solution {
    public boolean hasPathSum(TreeNode root, int targetSum) {
        // DFS(前序遍历 )
    	if(root == null) {
    		return false;
    	}
        List<Integer> pathVal = new ArrayList<>();	// 保存路径上节点的值,因为要回溯,所以函数参数应该也有路径上值的集合
        return getAllPathSum(root, pathVal, targetSum);
        
    }
    public boolean getAllPathSum(TreeNode node, List<Integer> pathVal, int targetSum) {
    	pathVal.add(node.val);
    	if(node.left == null && node.right == null) {	// 如果遇到叶子结点,说明要记录这条路径的和了
    		int sum = 0;
    		for(int i = 0; i < pathVal.size(); i++) {   // 求路径总和
    			sum += pathVal.get(i);
    		}
    		if(sum == targetSum) {
    			return true;
    		}
    	}
    	if(node.left != null) {
    		if(getAllPathSum(node.left, pathVal, targetSum)) {	// 如果找到了这条路径,那么就一直返回true即可
    			return true;
    		}else{
    			pathVal.remove(pathVal.size() - 1);
    		}
    	}
    	if(node.right != null) {
    		if(getAllPathSum(node.right, pathVal, targetSum)) {
    			return true;
    		}
    		else {
    			pathVal.remove(pathVal.size() - 1);
    		}
    	}
    	return false;       // 走到这,一般是叶子节点,返回false,到时候会让它的父节点走到回溯的
    }
}

但是当我看到提交之后只击败了15.14%的人!这可激发了我的好胜心!!!说明还能优化。

DFS法(纯享版):

仔细一想,这里也不需要用上LIst集合啊,这和257. 二叉树的所有路径不完全相同,那道题除了要打印值,还要打印一个"->"这个东东,所以用结合存储字符串好用来拼接,而且那道题也不是为了求和,关键是打印每一条路径,要体现各个节点的值,所以,最终重写了我的getAllPathSum方法,把方法参数改成只需要二叉树+目标和,也依然有回溯的影子,注意对比我这三个代码,总算对回溯有了一定滴认识啦。

代码:

class Solution {
	int pathValSum = 0;	// 保存某条路径上的和,放在外面是为了作为全局变量,可以累加和
    public boolean hasPathSum(TreeNode root, int targetSum) {
        // DFS(前序遍历 )
    	if(root == null) {
    		return false;
    	}
        return getAllPathSum(root, targetSum);
        
    }
    public boolean getAllPathSum(TreeNode node, int targetSum) {
    	pathValSum += node.val;
    	if(node.left == null && node.right == null) {	// 如果遇到叶子结点,判断目前这条路径上的和,是否等于目标值
    		if(pathValSum == targetSum) {
    			return true;
    		}/*
    		else{
    			pathValSum -= node.val;	
    								
    		} 这里不能这么写!!这样写并不是回溯!!,这样写的意思则表示遇到叶子节点不是目标值的才减叶子结点,
    		但是,我们的目的是一跳路径上的,这样减只减掉了叶子结点上的值,没有回溯
    		*/
    	}
    	if(node.left != null) {
    		if(getAllPathSum(node.left, targetSum)) {	// 如果找到了这条路径,那么就一直返回true即可
    			return true;
    		}else {
    			pathValSum -= node.left.val;					// 回溯!小子
    		}
    	}
    	if(node.right != null) {
    		if(getAllPathSum(node.right, targetSum)) {
    			return true;
    		}else {
    			pathValSum -= node.right.val;
    		}
    	}
    	return false;
    }
    
}

 嘿嘿~这下舒服了。

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

题目链接:106. 从中序与后序遍历序列构造二叉树

看完视频讲解和题解还算清晰了,关键是找分割,看完题解和视频后自己写了一下代码,直接用数组手撕的,过是过了,只是......

运行结果图:

 这执行时间!这消耗内存,属实是有点拉胯了,emmm,代码如下。

自己敲的代码:

/**
 * 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(postorder.length == 0) {
            return null;
        }
        int nodeVal = postorder[postorder.length - 1];  // 后序最后一个元素即是根节点值
        TreeNode node = new TreeNode(nodeVal);
        if(postorder.length == 1) {
            return node;
        }
        int delIndex = 0;
        // 先在中序遍历中找分割节点
        for(delIndex = 0; delIndex < inorder.length; delIndex++) {
            if(inorder[delIndex] == nodeVal) {
            	break;
            }
        }
        // 现在,在inorder中delIndex左边是左子树,右边是右子树
        // 下面是保存前序左区间,与右区间
        int[] inorderLeft = new int[delIndex];      // 注意大小,需要根据切割点来创建
        int[] inorderRight = new int[inorder.length - delIndex - 1];
        int inorderLeftSize = 0;
        int  inorderRightSize = 0;
        for(int i = 0; i < delIndex; i++) {
        	inorderLeft[inorderLeftSize++] = inorder[i];
        }
        for(int i = delIndex + 1; i < inorder.length; i++) {
        	inorderRight[inorderRightSize++] = inorder[i];
        }
        // 根据中序的大小,来切割后序左右区间
        int[] postorderLeft = new int[inorderLeftSize];     // 显然,后序遍历的左右区间大小和中序的左右区间大小是一样的,所以定义的时候直接用
        int[] postorderRight = new int[inorderRightSize];
        int postorderLeftSize = 0;
        int  postorderRightSize = 0;
        for(int i = 0; i < inorderLeftSize; i++) {
        	postorderLeft[postorderLeftSize++] = postorder[i];
        }
        for(int i = inorderLeftSize; i < postorder.length - 1; i++) {
        	postorderRight[postorderRightSize++] = postorder[i];
        }
        node.left = buildTree(inorderLeft, postorderLeft);  // 左子树,用中序的左区间+后序的左区间
        node.right = buildTree(inorderRight,postorderRight);
        return node;
    }
    
}

显然,有很多累赘的,或者说,很多代码复用性不强,像每次分割左右区间的时候,都会定义各自区间的大小,关键原因就在于,设计这个递归函数的时候,我就是直接用题目给的函数进行递归的,能过是能过,但是。。。。上面的运行时长就是结果。当然,这里可以优化的,也就是卡哥代码里给的,分割区间的时候,关键就在于:

1. 根据后序最后一个元素,在中序遍历中去“找”到这个这个元素对应位置,然后展开下面的分割。

2. 其实这些元素全部都在inorder数组和postorder数组中,只需要拿到了每个区间的始末,然后逐渐分割,利用后序遍历的最后一个元素就是节点的规则(new TreeNode的位置),去创建节点就是。

对应第一点,“找”,可以利用哈希法来找,因为哈希法最擅长“找”这个操作了,显然,这里需要根据找到下标,所以用map。然后第二点,便是把我上面代码中创建数组和分割数组,改成只通过下标来更改递归即可。根据上面这两点,可以写出下面的优化代码(直接拿代码随想录里的了):

优化代码:

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

在Meteor Lake上测试基于Stable Diffusion的AI应用

上个月刚刚推出的英特尔新一代Meteor Lake CPU&#xff0c;预示着AI PC的新时代到来。AI PC可以不依赖服务器直接在PC端处理AI推理工作负载&#xff0c;例如生成图像或转录音频。这些芯片的正式名称为Intel Core Ultra处理器&#xff0c;是首款配备专门用于处理人工智能任务的 …

外包干了8个月,技术退步明显...

先说一下自己的情况&#xff0c;大专生&#xff0c;18年通过校招进入武汉某软件公司&#xff0c;干了接近4年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落! 而我已经在一个企业干了四年的功能测…

Java 的 Map 與 List

通過重新new 一個ArrayList 轉化 resTask.setList(new ArrayList<Group>(custMap.values())); 无序的Map List 有序的数据放到Map&#xff0c;就变成无序。 List排序 按照code 的字母进行排序A-Z resTask.getListData().sort(Comparator.comparing(Gmer::getCode));…

深度强化学习(王树森)笔记08

深度强化学习&#xff08;DRL&#xff09; 本文是学习笔记&#xff0c;如有侵权&#xff0c;请联系删除。本文在ChatGPT辅助下完成。 参考链接 Deep Reinforcement Learning官方链接&#xff1a;https://github.com/wangshusen/DRL 源代码链接&#xff1a;https://github.c…

【论文阅读|半监督小苹果检测方法S3AD】

论文题目 &#xff1a; : Semi-supervised Small Apple Detection in Orchard Environments 项目链接&#xff1a;https://www.inf.uni-hamburg.de/en/inst/ab/cv/people/wilms/mad.html 摘要&#xff08;Abstract&#xff09; 农作物检测是自动估产或水果采摘等精准农业应用不…

盘点热门的GPTS智能体,生产力远超原生ChatGPT4

OPENAI开放了GPTS智能体商店&#xff0c;类似于appstore的应用商店&#xff0c;在GPTS商店里面你可以发现并创建自定义版本的ChatGPT&#xff0c;这些版本结合了指令、额外知识和任何技能组合&#xff01; 本周精选 GPTS智能体不仅可以通过API的方式将你的私有化的数据和能力…

双链表的基本知识以及增删查改的实现

满怀热忱&#xff0c;前往梦的彼岸 前言 之前我们对单链表进行了非常细致的剖析&#xff0c;现在我们所面临的则是与之相对应的双链表&#xff0c;我会先告诉诸位它的基本知识&#xff0c;再接着把它的增删查改讲一下&#xff0c;ok&#xff0c;正文开始。 一.链表的种类 我…

机器学习和深度学习中的normalization(归一化)

在机器学习和深度学习中&#xff0c;normalization&#xff08;归一化&#xff09;是一种重要的数据预处理步骤&#xff0c;它的目的是改变数值数据的形式&#xff0c;以使其在一个固定的范围内&#xff0c;通常是 0 到 1&#xff0c;或者使其均值为 0&#xff0c;标准差为 1。…

Jenkins+Python自动化测试持续集成详细教程

&#x1f525; 交流讨论&#xff1a;欢迎加入我们一起学习&#xff01; &#x1f525; 资源分享&#xff1a;耗时200小时精选的「软件测试」资料包 &#x1f525; 教程推荐&#xff1a;火遍全网的《软件测试》教程 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1…

基于Prompt Learning的信息抽取

PTR: Prompt Tuning with Rules for Text Classification 清华&#xff1b;liuzhiyuan&#xff1b;通过规则制定subpromptRelation Extraction as Open-book Examination: Retrieval-enhanced Prompt Tuning Relation Extraction as Open-book Examination: Retrieval-enhance…

JSP在线阅读系统myeclipse定制开发SQLServer数据库网页模式java编程jdbc

一、源码特点 JSP 小说在线阅读系统是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库 &#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为SQLServer2008&#…

KubeSphere 核心实战之四【在kubesphere平台上部署Ruoyi-cloud项目】(实操篇 4/4)

**《KubeSphere 核心实战系列》** KubeSphere 核心实战之一&#xff08;实操篇 1/4&#xff09; KubeSphere 核心实战之二&#xff08;实操篇 2/4&#xff09; KubeSphere 核心实战之三&#xff08;实操篇 3/4&#xff09; KubeSphere 核心实战之四&#xff08;实操篇 4/4&…

学会用Python分割、合并字符串

在很多情况下&#xff0c;我们需要对字符串进行分割或合并&#xff0c;以满足特定的需求&#xff0c;例如将字符串拆分成多个部分、将多个字符串合并成一个等等。Python提供了多种方法来进行字符串的分割和合并&#xff0c;本文将介绍其中几种常用的方法。 一、使用split()函数…

深度解析C++引用究竟是什么

首先看一段代码&#xff1a; #include<iostream> using namespace std; void fun(int*a){} void fun(int&a){} int main(){int a10;fun(&a);fun(a); }我们查看它的汇编结果时会发现引用和指针传参整个过程的汇编是一模一样的 我们再看下面这段代码&#xff1a…

HBase(docker版)简单部署和HBase shell操作实践

文章目录 说明HBase部署访问HBase Shell常见命令数据定义语言(DDL) 数据操作语言(DML)通用操作访问HBase WebUI 说明 本文适合HBase初学者快速搭建HBase环境&#xff0c;练习常见shell使用本文参考资料 《大数据技术原理和应用》&#xff08;林子雨 编著 第三版&#xff09;zh…

C++ 滑动窗口

目录 1、209. 长度最小的子数组 2、3. 无重复字符的最长子串 3、1004. 最大连续1的个数 III 4、1658. 将 x 减到 0 的最小操作数 5、904. 水果成篮 6、438. 找到字符串中所有字母异位词 7、30. 串联所有单词的子串 8、76. 最小覆盖子串 1、209. 长度最小的子数组 思路&…

存储监控工具:监控存储区域网络(SAN)

从托管应用程序到提供大型多媒体服务&#xff0c;组织都依靠其 IT 基础架构来提供无与伦比的最终用户体验。为了提供这种卓越的体验&#xff0c;必须大大提高应用程序的可用性和性能。在许多其他挑战中&#xff0c;存储区域网络 &#xff08;SAN&#xff09; 正好用于应对这些挑…

TPCC-MySQL

简介 TPC-C是专门针对联机交易处理系统&#xff08;OLTP系统&#xff09;的规范&#xff0c;一般情况下我们也把这类系统称为业务处理系统。 Tpcc-mysql是percona基于TPC-C(下面简写成TPCC)衍生出来的产品&#xff0c;专用于MySQL基准测试。其源码放在launchpad上&#xff0c…

使用xlsx、xlsx-style导出表格添加背景色;合并单元格部分样式缺失问题解决

这篇说一下使用xlsx-style导出excel时样式的设置。需要安装xlsx、xlsx-style、file-saver插件&#xff08;file-saver可以不装&#xff0c;用a标签代替也可以&#xff09;&#xff0c;安装时可能会碰到一些报错问题&#xff0c;可以去看下我之前一篇博客&#xff1a;纯前端导出…

C语言之刷到的怪题(i与sizeof(i)比较大小)

这个题目一般都是选择输出<。为什么呢&#xff1f;因为i是一个全局变量&#xff0c;并且没有初始化&#xff0c;那么i的值就等于0。i--之后就是-1了。而sizeof(i)求出的就是整形变量对应的大小4个字节。-1<4&#xff0c;因此就选择 输出<。其实不然&#xff0c;这个si…
最新文章