题目:
给定一个二叉树的 根节点
root
,请找出该二叉树的 最底层 最左边 节点的值。假设二叉树中至少有一个节点。
示例 1:
输入: root = [2,1,3] 输出: 1示例 2:
输入: [1,2,3,4,null,5,6,null,null,7] 输出: 7提示:
- 二叉树的节点个数的范围是
[1,104]
-231 <= Node.val <= 231 - 1
[LeetCode] 513. 找树左下角的值
自己看到题目的第一想法
递归法:
1. 不停的一左节点优先的顺序向下遍历每一个节点, 同时记录当前遍历的节点的深度.
2. 同时用 maxDepth 记录当前遍历过的节点中, 深度最大的那一个. 当遍历到一个节点的深度大雨 maxDepth 时, 用 maxDepth 保存这个节点, 同时用 value 记录住节点的值.
3. 遍历完成后返回 value.
迭代法:
1. 层序遍历法
2. 标记法: 需要带回溯的, 可以用标记法.
看完代码随想录之后的想法
这一题没有出现太多的偏差
/**
* 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 int value;
private int maxDepth = 0;
public int findBottomLeftValue(TreeNode root) {
findBottomLeftValue(root, 1);
return value;
}
public void findBottomLeftValue(TreeNode root, int depth) {
if (root == null) {
return;
}
if (root.left == null && root.right == null) {
if (depth > maxDepth) {
maxDepth = depth;
value = root.val;
return;
}
}
if (root.left != null) {
findBottomLeftValue(root.left, depth + 1);
}
if (root.right != null) {
findBottomLeftValue(root.right, depth + 1);
}
}
}
// 迭代法: 层序遍历. 效率低: 55.16%
class Solution {
public int findBottomLeftValue(TreeNode root) {
LinkedList<TreeNode> nodes = new LinkedList<>();
TreeNode node = null;
int size = 0;
nodes.push(root);
int value = 0;
while (!nodes.isEmpty()) {
size = nodes.size();
value = nodes.peek().val;
while (size-- > 0) {
node = nodes.pop();
if (node.left != null) {
nodes.addLast(node.left);
}
if (node.right != null) {
nodes.addLast(node.right);
}
}
}
return value;
}
}
// 迭代法: 标记法. 效率低: 5.6%
class Solution {
public int findBottomLeftValue(TreeNode root) {
Stack<TreeNode> nodes = new Stack<>();
TreeNode node;
nodes.push(root);
int depth = 0;
int maxDepth = 0;
int value = 0;
while (!nodes.isEmpty()) {
node = nodes.pop();
if (node != null) {
nodes.push(node);
nodes.push(null);
depth++;
if (node.right != null) {
nodes.push(node.right);
}
if (node.left != null) {
nodes.push(node.left);
}
} else {
node = nodes.pop();
if (node.left == null && node.right == null) {
if (depth > maxDepth) {
maxDepth = depth;
value = node.val;
}
}
depth--;
node = null;
}
}
return value;
}
}
自己实现过程中遇到哪些困难
无(也可能是记不得了)
给你二叉树的根节点
root
和一个表示目标和的整数targetSum
。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和targetSum
。如果存在,返回true
;否则,返回false
。叶子节点 是指没有子节点的节点。
示例 1:
输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22 输出:true 解释:等于目标和的根节点到叶节点路径如上图所示。示例 2:
输入: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
[LeetCode] 112. 路径总和
自己看到题目的第一想法
1. 遍历所有节点, 并记录当前路径上所有节点的和. 如果发现当前节点为叶子结点, 并且加上当前节点的值后, 总和与要预期的总和相同, 则返回 true, 表示存在一条路径, 当前路径的所有节点的值相加起来和总和相同.
看完代码随想录之后的想法
1. 递归法: 想法是一致的
2. 迭代法: 我用了标记法. 随想录使用 Pair<Node, sum> 记录住了当前节点对应的路径总和的值, 这样也挺好的, 比笔记法少了几个递归, 应该是更高效的.
// 递归法
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
return hasPathSum(root, targetSum, 0);
}
private boolean hasPathSum(TreeNode node, int targetSum, int sum) {
if (node == null) {
return false;
}
sum += node.val;
if (node.left == null && node.right == null && sum == targetSum) {
return true;
}
return hasPathSum(node.left, targetSum, sum) || hasPathSum(node.right, targetSum, sum);
}
}
// 迭代法
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
if (root == null) {
return false;
}
Stack<TreeNode> nodes = new Stack<>();
TreeNode node = null;
nodes.push(root);
int sum = 0;
while (!nodes.isEmpty()) {
node = nodes.pop();
if (node != null) {
nodes.push(node);
nodes.push(null);
sum += node.val;
if (node.right != null) {
nodes.push(node.right);
}
if (node.left != null) {
nodes.push(node.left);
}
} else {
node = nodes.pop();
if (node.left == null && node.right == null && sum == targetSum) {
return true;
}
sum -= node.val;
node = null;
}
}
return false;
}
}
// 迭代法: 封装对象.
// 将每个节点封装到 Pair 中,
// first 为 TreeNode 本身, second 为 到这个节点的 sum 的和
// 然后用后序遍历的方式展开所有节点.
// 这样再遇到叶子结点的时候, 检查 Pair.second == targetSum 即可
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
// 未实现
}
}
自己实现过程中遇到哪些困难
主要是会有些迷茫. 一个一个的去套模板方法, 经常能发现有一个是能行的. 但是对于什么时候回溯, 如何回溯, 总感觉是蒙了一层薄膜的感觉. 不是特别的清晰.
题目:
给你二叉树的根节点
root
和一个整数目标和targetSum
,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。叶子节点 是指没有子节点的节点。
示例 1:
输入: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:
输入:root = [1,2,3], targetSum = 5 输出:[]示例 3:
输入:root = [1,2], targetSum = 0 输出:[]提示:
- 树中节点总数在范围
[0, 5000]
内-1000 <= Node.val <= 1000
-1000 <= targetSum <= 1000
[LeetCode] 113. 路径总和 II
自己看到题目的第一想法
1. 依旧是遍历所有节点, 但是不需要返回值了, 因为需要的节点都在路径列表里保存以及删除
2. 遍历节点右很多种顺序, 这里前序会方便一些.
3. 因为需要回溯, 因此这里可以使用 “递归法“ 或者 “标记法”.
看完代码随想录之后的想法
有些忘记了, 大体上可能是差不多的. 最近的题目不是特别需要看文章解释.
// 递归法
class Solution {
List<List<Integer>> result = null;
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
result = new ArrayList<>();
if (root == null) {
return result;
}
pathSum(root, targetSum, new ArrayList<Integer>(), 0);
return result;
}
private void pathSum(TreeNode root, int targetSum, List<Integer> path, int sum) {
if (root == null) {
return;
}
sum += root.val;
path.add(root.val);
if (root.left == null && root.right == null && sum == targetSum) {
result.add(new ArrayList<Integer>(path));
return;
}
if (root.right != null) {
pathSum(root.right, targetSum, path, sum);
path.remove(path.size() - 1);
}
if (root.left != null) {
pathSum(root.left, targetSum, path, sum);
path.remove(path.size() - 1);
}
}
}
// 迭代法:
class Solution {
List<List<Integer>> result = null;
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
result = new ArrayList<>();
if (root == null) {
return result;
}
Stack<TreeNode> nodes = new Stack<>();
List<Integer> path = new ArrayList<>();
int sum = 0;
TreeNode node;
nodes.push(root);
while (!nodes.isEmpty()) {
node = nodes.pop();
if (node != null) {
nodes.push(node);
nodes.push(null);
path.add(node.val);
sum += node.val;
if (node.right != null) {
nodes.push(node.right);
}
if (node.left != null) {
nodes.push(node.left);
}
} else {
node = nodes.pop();
if (node.left == null && node.right == null && sum == targetSum) {
result.add(new ArrayList<>(path));
}
path.remove(path.size() - 1);
sum -= node.val;
node = null;
}
}
return result;
}
}
自己实现过程中遇到哪些困难
无
[LeetCode] 106. 从中序与后序遍历序列构造二叉树
[LeetCode] 106. 从中序与后序遍历序列构造二叉树 文章解释
[LeetCode] 106. 从中序与后序遍历序列构造二叉树 视频解释
题目:
给定两个整数数组
inorder
和postorder
,其中inorder
是二叉树的中序遍历,postorder
是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。示例 1:
输入: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]提示:
1 <= inorder.length <= 3000
postorder.length == inorder.length
-3000 <= inorder[i], postorder[i] <= 3000
inorder
和postorder
都由 不同 的值组成postorder
中每一个值都在inorder
中inorder
保证是树的中序遍历postorder
保证是树的后序遍历
[LeetCode] 106. 从中序与后序遍历序列构造二叉树
自己看到题目的第一想法
1. 很经典的题目
2. 看过
3. 不会...(小声一点)
看完代码随想录之后的想法
1. 如此简单
2. 对于从遍历结果反推二叉树, 中序遍历是决定性的存在, 因为只有中序遍历, 才能将前序遍历、中序遍历中, 根节点的左右子树的序列划分出来. 前序遍历、后序遍历有个特点就是, 除了根节点, 剩余的节点遵循了一个规律: 从左到右的顺序, 左边的都是左子树的节点, 右边的都是右子树的节点. 因此通过中序遍历, 我们可以很容易的从前序、后序遍历中拿到左右子树对应的节点.
3. 同时, 对于前序遍历, 从左往右的每一个当前节点, 分别是当前中序遍历序列的根节点. 这里得模拟一下二叉树的遍历过程, 才能想明白. 或者我得画个图才能说清楚.
// 迭代法1:
class Solution {
private int postOrderIndex = 0;
public TreeNode buildTree(int[] inorder, int[] postorder) {
Map<Integer, Integer> nodeIndexs = new HashMap<>();
for (int i = 0; i < inorder.length; i++) {
nodeIndexs.put(inorder[i], i);
}
postOrderIndex = postorder.length - 1;
return buildTree(inorder, postorder, nodeIndexs, 0, inorder.length - 1);
}
private TreeNode buildTree(int[] inorder, int[] postorder, Map<Integer, Integer> nodeIndexs, int startIndex, int endIndex) {
if (startIndex > endIndex) {
return null;
}
// 注意这里的 postOrderIndex--
TreeNode root = new TreeNode(postorder[postOrderIndex--]);
// 下面两行的顺序不能变, 一定要先遍历中序遍历的右侧部分的节点.
// 中序遍历为 左 中 右, 而右侧部分如果是后序遍历的话, 则是 左 右 中
// 因此对于完整的后序遍历序列, 如果按照 中 =》 右 =》 左 的顺序来回溯
// 后序遍历从右往左, 就依次是每次迭代时的根节点.
root.right = buildTree(inorder, postorder, nodeIndexs, nodeIndexs.get(root.val) + 1, endIndex);
root.left = buildTree(inorder, postorder, nodeIndexs, startIndex, nodeIndexs.get(root.val) - 1);
return root;
}
}
// 迭代法2:
class Solution {
private Map<Integer, Integer> nodeIndexs = null;
public TreeNode buildTree(int[] inorder, int[] postorder) {
nodeIndexs = new HashMap<>();
for (int i = 0; i < inorder.length; i++) {
nodeIndexs.put(inorder[i], i);
}
return buildTree(inorder, postorder, 0, inorder.length - 1, 0, inorder.length - 1);
}
private TreeNode buildTree(int[] inorder, int[] postorder,
int inOrderStartIndex, int inOrderEndIndex,
int postOrderStartIndex, int postOrderEndIndex) {
if (inOrderStartIndex > inOrderEndIndex) {
return null;
}
int middleNodeValue = postorder[postOrderEndIndex];
int middleNodeIndex = nodeIndexs.get(middleNodeValue);
TreeNode root = new TreeNode(middleNodeValue);
root.left = buildTree(inorder, postorder, inOrderStartIndex, middleNodeIndex - 1,
postOrderStartIndex, postOrderStartIndex + middleNodeIndex - inOrderStartIndex - 1);
root.right = buildTree(inorder, postorder, middleNodeIndex + 1, inOrderEndIndex,
postOrderStartIndex + middleNodeIndex - inOrderStartIndex, postOrderEndIndex - 1);
return root;
}
}
自己实现过程中遇到哪些困难
后序和中序对应的序列区间的索引, 经常计算错误, 导致死循环.
[LeetCode] 105. 从前序与中序遍历序列构造二叉树
[LeetCode] 105. 从前序与中序遍历序列构造二叉树 文章解释
题目:
给定两个整数数组
preorder
和inorder
,其中preorder
是二叉树的先序遍历,inorder
是同一棵树的中序遍历,请构造二叉树并返回其根节点。示例 1:
输入: 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
preorder
和inorder
均 无重复 元素inorder
均出现在preorder
preorder
保证 为二叉树的前序遍历序列inorder
保证 为二叉树的中序遍历序列
[LeetCode] 105. 从前序与中序遍历序列构造二叉树
自己看到题目的第一想法
和后序+中序遍历一样的逻辑, 修改一下获取根节点值和索引的逻辑就行.
看完代码随想录之后的想法
// 解法1: 注意每次迭代的单词循环逻辑中, 获取根节点的逻辑
class Solution {
private int[] preorder;
private int[] inorder;
private Map<Integer, Integer> inorderNodeIndexs = new HashMap<>();
private int middleNodeIndex = 0;
public TreeNode buildTree(int[] preorder, int[] inorder) {
this.preorder = preorder;
this.inorder = inorder;
for (int i = 0; i < inorder.length; i++) {
inorderNodeIndexs.put(inorder[i], i);
}
return buildTree(0, inorder.length - 1);
}
private TreeNode buildTree(int inorderStartIndex, int inorderEndIndex) {
if (inorderStartIndex > inorderEndIndex) {
return null;
}
// 注意这里的 middleNodeIndex++
TreeNode root = new TreeNode(preorder[middleNodeIndex++]);
int middleNodeIndex = inorderNodeIndexs.get(root.val);
root.left = buildTree(inorderStartIndex, middleNodeIndex - 1);
root.right = buildTree(middleNodeIndex + 1, inorderEndIndex);
return root;
}
}
// 递归解法2
class Solution {
private int[] preorder;
private int[] inorder;
private Map<Integer, Integer> inorderNodeIndexs = new HashMap<>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
this.preorder = preorder;
this.inorder = inorder;
for (int i = 0; i < inorder.length; i++) {
inorderNodeIndexs.put(inorder[i], i);
}
return buildTree(0, inorder.length - 1, 0, inorder.length - 1);
}
private TreeNode buildTree(int inorderStart, int inorderEnd,
int preorderStart, int preorderEnd) {
if (inorderStart > inorderEnd) {
return null;
}
TreeNode root = new TreeNode(preorder[preorderStart]);
int middleNodeIndex = inorderNodeIndexs.get(root.val);
root.left = buildTree(inorderStart, middleNodeIndex - 1, preorderStart + 1, preorderStart + middleNodeIndex - inorderStart);
root.right = buildTree(middleNodeIndex + 1, inorderEnd, preorderStart + middleNodeIndex - inorderStart + 1, preorderEnd);
return root;
}
}
自己实现过程中遇到哪些困难
因为先写了后序+中序的逻辑, 这里就是简单的复制一下, 短时间记忆让自己背下来了.