目录
LeetCode.513 找树左下角的值
题目链接 找树左下角的值
题解一 递归法
class Solution {
int maxDeep = 0;
int leftBottomValue;
public int findBottomLeftValue(TreeNode root) {
leftBottomValue = root.val;
find(root,1);
return leftBottomValue;
}
public void find(TreeNode node,int deep){
if(node == null){
return ;
}
if(node.left == null && node.right == null && deep > maxDeep){
leftBottomValue = node.val;
maxDeep = deep;
}
find(node.left,deep + 1);
find(node.right,deep + 1);
}
}
解题思路
这段代码的核心思路是借助深度优先搜索(DFS)来找出二叉树最底层最左边节点的值。具体逻辑如下:
-
变量设定:
maxDeep
:用于记录当前所探寻到的最大深度,初始值设为 0。leftBottomValue
:用于存储最底层最左边节点的值,一开始将其初始化为根节点的值。
-
方法功能:
findBottomLeftValue
:作为主方法,先把leftBottomValue
初始化为根节点的值,接着调用递归方法find
来更新结果,最后返回结果。find
:这是一个递归辅助方法,会对每个节点进行遍历。当遇到叶子节点(即没有左右子节点的节点)时,检查该节点的深度是否比当前记录的最大深度maxDeep
还要大。若更大,就更新maxDeep
和leftBottomValue
。递归时,先处理左子树,再处理右子树,这样能保证在同一深度下,左子树的节点会优先被访问到。
-
递归策略:
- 采用先左后右的前序遍历方式,确保在每一层中,左子树的节点会最先被处理。
- 当遍历到叶子节点时,如果该叶子节点的深度超过了之前记录的最大深度,就更新结果为该叶子节点的值。由于是先遍历左子树,所以在同一深度下,最左边的节点会最先被更新。
-
初始值的作用:
- 当二叉树只有一个根节点时,根节点同时也是最底层最左边的节点,此时直接返回根节点的值。
关键点:
- 利用深度优先搜索并结合递归的方式,优先遍历左子树,以此保证能找到最底层最左边的节点。
- 通过记录最大深度,保证每次更新的节点都是当前最深层的节点。
- 初始值的设置保证了在二叉树只有一个节点的情况下,结果依然正确。
题目二 迭代法
class Solution {
public int findBottomLeftValue(TreeNode root) {
int res = 0;
Queue<TreeNode> que = new LinkedList<TreeNode>();
que.offer(root);
while(!que.isEmpty()){
int len = que.size();
for(int i = 0;i<len;i++){
TreeNode tmpNode = que.poll();
if(i == 0){
res = tmpNode.val;
}
if(tmpNode.left != null) que.offer(tmpNode.left);
if(tmpNode.right != null) que.offer(tmpNode.right);
}
}
return res;
}
}
解题思路
这段代码的解题思路是使用广度优先搜索(BFS)层序遍历来找到二叉树最底层的最左边节点的值。以下是详细的步骤解释:
核心思路
- 层序遍历:利用队列逐层遍历二叉树,确保每一层的节点按从左到右的顺序被处理。
- 记录每层最左节点:在每一层遍历开始时,队列的第一个元素即为该层的最左边节点。通过每次处理一层时记录第一个节点的值,最终遍历到最后一层时,记录的值即为最底层的最左节点。
代码逻辑
-
初始化:
res
:用于存储每一层的最左节点的值,初始化为 0。Queue<TreeNode>
:使用队列来进行层序遍历,初始时将根节点加入队列。
-
层序遍历循环:
- 外层循环:每次处理一层的所有节点,循环条件为队列非空。
- 记录当前层节点数:通过
que.size()
获取当前层的节点数量len
。 - 内层循环:遍历当前层的所有节点(共
len
个)。- 取出队首节点:处理当前节点
tmpNode
。 - 记录最左节点:当
i == 0
时,说明当前节点是该层的第一个节点(即最左边节点),更新res
为该节点的值。 - 添加子节点:将当前节点的左子节点和右子节点(若存在)依次加入队列。
- 取出队首节点:处理当前节点
-
返回结果:
- 遍历完所有层后,
res
中存储的即为最后一层的最左节点的值。
- 遍历完所有层后,
关键点
- 层序遍历的顺序:BFS 保证了每层节点按从左到右的顺序被处理,因此每层的第一个节点即为该层的最左节点。
- 更新机制:每次进入新的一层时,
res
会被更新为该层的最左节点的值。由于最后一层是最后被处理的,因此最终res
存储的是最后一层的最左节点的值。 - 无需额外判断深度:层序遍历自然地从根到叶逐层处理,最后一层的最左节点会在遍历结束时被记录,无需显式跟踪深度。
LeetCode.112 路径总和
题目链接 路径总和
题解
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
if(root == null){
return false;
}
targetSum -= root.val;
if(root.left == null && root.right == null){
return targetSum == 0;
}
if(root.left != null){
boolean left = hasPathSum(root.left,targetSum);
if(left){
return true;
}
}
if(root.right != null){
boolean right = hasPathSum(root.right,targetSum);
if(right){
return true;
}
}
return false;
}
}
解题思路
这段代码的解题思路是使用 深度优先搜索(DFS) 来判断二叉树中是否存在一条从根节点到叶子节点的路径,使得该路径上所有节点的值之和等于给定的目标和 targetSum
。以下是详细的步骤解释:
核心思路
- 递归遍历路径:从根节点开始,递归地检查每个节点的左右子树,同时更新剩余的目标和(即
targetSum
减去当前节点的值)。 - 终止条件:
- 若当前节点为空,返回
false
(路径不存在)。 - 若当前节点是叶子节点(即无左右子节点),检查剩余目标和是否为 0。若为 0,则说明当前路径的和等于目标和,返回
true
;否则返回false
。
- 若当前节点为空,返回
- 递归逻辑:
- 若当前节点有左子树,递归检查左子树的路径是否满足条件。若满足,直接返回
true
。 - 若当前节点有右子树,递归检查右子树的路径是否满足条件。若满足,直接返回
true
。 - 若左右子树均未找到满足条件的路径,返回
false
。
- 若当前节点有左子树,递归检查左子树的路径是否满足条件。若满足,直接返回
代码逻辑
-
处理空节点:
- 若根节点为空,直接返回
false
。
- 若根节点为空,直接返回
-
更新目标和:
- 将
targetSum
减去当前节点的值,得到剩余需要的和。
- 将
-
检查叶子节点:
- 若当前节点是叶子节点(即
root.left == null && root.right == null
),检查剩余目标和是否为 0。若是,返回true
;否则返回false
。
- 若当前节点是叶子节点(即
-
递归检查子树:
- 左子树:若左子树存在,递归调用
hasPathSum(root.left, targetSum)
。若结果为true
,说明左子树中存在满足条件的路径,直接返回true
。 - 右子树:同理,若右子树存在且递归调用
hasPathSum(root.right, targetSum)
结果为true
,返回true
。
- 左子树:若左子树存在,递归调用
-
最终结果:
- 若左右子树均未找到满足条件的路径,返回
false
。
- 若左右子树均未找到满足条件的路径,返回
关键点
- 路径定义:路径必须从根节点开始,到叶子节点结束。因此,只有当节点同时满足无左右子节点时,才检查剩余和是否为 0。
- 剪枝优化:一旦在左子树或右子树中找到满足条件的路径,立即返回
true
,避免继续遍历其他路径。 - 目标和更新:每次递归调用时,
targetSum
会减去当前节点的值,确保路径和的计算正确。
LeetCode.113 路径总和Ⅱ
题目链接 路径总和Ⅱ
题解
class Solution {
List<List<Integer>> resList = new ArrayList<>();
List<Integer> res = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
if(root == null){
return resList;
}
preOrderDfs(root,targetSum);
return resList;
}
public void preOrderDfs(TreeNode root,int count){
res.add(root.val);
if(root.left == null && root.right == null){
if(count - root.val == 0){
resList.add(new ArrayList<>(res));
}
return;
}
if(root.left != null){
preOrderDfs(root.left,count - root.val);
res.remove(res.size() - 1);
}
if(root.right != null){
preOrderDfs(root.right,count - root.val);
res.remove(res.size() - 1);
}
}
}
解题思路
这段代码的解题思路是使用深度优先搜索(DFS)结合回溯法,找出二叉树中所有从根节点到叶子节点的路径,使得路径上节点值的和等于给定的目标和 targetSum
。以下是详细的步骤解释:
核心思路
- 递归遍历路径:从根节点开始,递归地遍历每个节点,同时维护当前路径和剩余目标和。
- 回溯法维护路径:使用一个列表
res
记录当前路径上的节点值。在递归返回时,需要移除当前节点的值(回溯),以确保路径的正确性。 - 路径记录条件:当遍历到叶子节点时,检查剩余目标和是否为 0。若为 0,则将当前路径加入结果列表
resList
。
代码逻辑
-
初始化:
resList
:存储所有满足条件的路径。res
:存储当前递归路径上的节点值。
-
主方法
pathSum
:- 处理根节点为空的情况,直接返回空列表。
- 调用
preOrderDfs
开始深度优先搜索。
-
递归方法
preOrderDfs
:- 路径更新:将当前节点的值加入
res
。 - 叶子节点判断:若当前节点是叶子节点,检查剩余目标和是否为 0。若是,则将当前路径的副本加入
resList
。 - 递归遍历子树:
- 若左子树存在,递归处理左子树,并在返回后移除左子树节点(回溯)。
- 若右子树存在,递归处理右子树,并在返回后移除右子树节点(回溯)。
- 路径更新:将当前节点的值加入
LeetCode.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.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 index = map.get(postorder[postEnd - 1]);
int leftOfLen = index - inBegin;
TreeNode rootNode = new TreeNode(inorder[index]);
TreeNode leftRoot = findNode(inorder,inBegin,index,postorder,postBegin,postBegin + leftOfLen);
TreeNode rightRoot = findNode(inorder,index + 1,inEnd,postorder,postBegin + leftOfLen,postEnd -1);
rootNode.left = leftRoot;
rootNode.right = rightRoot;
return rootNode;
}
}
解题思路
这段代码的解题思路是利用 ** 中序遍历(inorder)和后序遍历(postorder)** 的特性,递归地构建二叉树。以下是详细的步骤解释:
核心思路
-
后序遍历的特性: 后序遍历的顺序是 左子树 → 右子树 → 根节点,因此后序数组的最后一个元素必定是当前子树的根节点。
-
中序遍历的特性: 中序遍历的顺序是 左子树 → 根节点 → 右子树,因此可以通过根节点将中序数组划分为 左子树部分 和 右子树部分。
-
递归构建:
- 通过后序数组找到根节点的值。
- 在中序数组中定位根节点的位置,确定左右子树的节点数量。
- 递归构建左右子树,更新对应的索引范围。
代码逻辑
-
哈希表预处理:
- 使用哈希表
map
存储中序数组中每个值的索引,以便快速定位根节点在中序数组中的位置。
- 使用哈希表
-
主方法
buildTree
:- 初始化哈希表,将中序数组的值和索引存入
map
。 - 调用递归函数
findNode
,传入中序和后序数组的初始范围。
- 初始化哈希表,将中序数组的值和索引存入
-
递归方法
findNode
:- 终止条件:若索引范围无效(
inBegin >= inEnd
或postBegin >= postEnd
),返回null
。 - 确定根节点:后序数组的最后一个元素
postorder[postEnd - 1]
即为当前子树的根节点。 - 划分左右子树:
- 在中序数组中找到根节点的索引
index
。 - 计算左子树的节点数量
leftOfLen = index - inBegin
。
- 在中序数组中找到根节点的索引
- 递归构建左右子树:
- 左子树:中序范围为
[inBegin, index)
,后序范围为[postBegin, postBegin + leftOfLen)
。 - 右子树:中序范围为
[index + 1, inEnd)
,后序范围为[postBegin + leftOfLen, postEnd - 1)
。
- 左子树:中序范围为
- 终止条件:若索引范围无效(
关键点
-
索引范围的确定:
- 后序数组中,左子树的长度与中序数组中左子树的长度相同,因此可以通过
leftOfLen
划分左右子树的范围。 - 注意递归时的索引边界:左闭右开区间(如
[inBegin, inEnd)
),确保不越界。
- 后序数组中,左子树的长度与中序数组中左子树的长度相同,因此可以通过
-
递归逻辑:
- 每次递归时,后序数组的最后一个元素作为根节点,通过中序数组划分左右子树,缩小问题规模。
LeetCode.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.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 rootNode = new TreeNode(inorder[rootIndex]);
int leftOfLen = rootIndex - inBegin;
rootNode.left = findNode(preorder,preBegin + 1,preBegin + 1 + leftOfLen,inorder,inBegin,rootIndex);
rootNode.right = findNode(preorder,preBegin + 1 + leftOfLen,preEnd,inorder,rootIndex + 1,inEnd);
return rootNode;
}
}
题目链接
思路如LeetCode.106。