目录
【513.找树左下角的值】 中等题
注意:最底层最左边节点的值不一定是左节点的值,也有可能是右节点的值。
方法一 层序迭代(很简单)
思路:记录每一层遍历的第一个节点(i = 0的节点)的val
/**
* 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) {
// 层序迭代(前提:至少有一个节点,即 root != null)
Deque<TreeNode> queue = new ArrayDeque<>();
TreeNode cur = root;
queue.offer(cur);
int res = cur.val;
while (!queue.isEmpty()){
int size = queue.size();
for(int i = 0; i < size; i++){
cur = queue.poll();
if (cur.left != null) queue.offer(cur.left);
if (cur.right != null) queue.offer(cur.right);
if (i == 0) res = cur.val; // 记录每行从左边数来的第一个节点的val
}
}
return res;
}
}
方法二 递归 - 前序遍历
思路:
1、确定参数和返回值
- 传入当前节点及其所在深度,目的是为了记录叶子节点的最大深度,不需要返回值,直接更改res成员变量。
2、确定终止条件
- if (root == null) return;
3、确定单层递归逻辑
- 如果当前节点是叶子节点且深度最深,则记录该叶子节点的val。(此时的叶子节点肯定是该层最左边的叶子节点)
- 继续遍历左右节点(找的是最左边的值,所以必须先遍历左节点,再遍历右节点)
/**
* 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 {
int res;
int maxDepth = 0;
public int findBottomLeftValue(TreeNode root) {
res = root.val;
getBottomLeft(root, 1);
return res;
}
// 1、确定参数和返回值:传入当前节点及其所在深度,目的是为了记录叶子节点的最大深度,不需要返回值,直接更改res成员变量
public void getBottomLeft(TreeNode root, int depth){
// 2、确定终止条件
if (root == null) return;
// 3、确定单层递归逻辑:如果当前节点是叶子节点且深度最深,则记录该叶子节点的val。(此时的叶子节点肯定是该层最左边的叶子节点)
if (root.left == null && root.right == null && depth > maxDepth){
res = root.val;
maxDepth = depth;
}
// 继续遍历左右节点(找的是最左边的值,所以必须先遍历左节点,再遍历右节点)
getBottomLeft(root.left, depth + 1);
getBottomLeft(root.right, depth + 1);
}
}
【112. 路径总和】简单题
方法一 递归 - 前序遍历(按照【257. 二叉树的所有路径】的思路)
思路:
1、确定参数和返回值
- 传入当前节点和遍历到该节点前的路径之和
2、确定终止条件
- if (root == null) return;
3、确定单层递归逻辑
- 如果当前节点为叶子节点,且路径之和 = targetSum,则修改结果为true
- 继续遍历左右节点
注意:
- 需要用成员变量存储targetSum,否则在递归方法中无法访问targetSum
- 如果没有路径的和 = targetSum,则res未被修改,返回默认值false
/**
* 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 {
boolean res = false;
int target;
public boolean hasPathSum(TreeNode root, int targetSum) {
target = targetSum; // 需要用成员变量存储targetSum,否则在递归方法中无法访问targetSum
findTargetSum(root, 0);
return res; // 如果没有路径的和 = targetSum,则res未被修改,返回默认值false
}
// 1、确定参数和返回值:传入当前节点和遍历到该节点前的路径之和
public void findTargetSum(TreeNode root, int sum){
// 2、确定终止条件
if (root == null) return;
// 3、确定单层递归逻辑
// 如果当前节点为叶子节点,且路径之和 = targetSum,则修改结果为true
if (root.left == null && root.right == null && sum + root.val == target) res = true;
// 继续遍历左右节点
findTargetSum(root.left, sum + root.val);
findTargetSum(root.right, sum + root.val);
}
}
方法二 递归 - 前序遍历(直接利用原来的hasPathSum实现递归)
关键:确定单层递归逻辑
- A、如果当前节点为叶子节点,判断路径之和是否为targetSum,由于用的是减法,则判断减完该节点的val后是否为0
- B、如果左节点返回的是true,证明当前节点所在路径就是true,直接返回true
- C、如果右节点返回的是true,证明当前节点所在路径就是true,直接返回true
思路版
/**
* 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) {
if (root == null) return false;
// 确定单层逻辑
// A、如果当前节点为叶子节点,判断路径之和是否为targetSum,由于用的是减法,则判断减完该节点的val后是否为0
if (root.left == null && root.right == null) return targetSum - root.val == 0;
// B、如果左节点返回的是true,证明当前节点所在路径就是true,直接返回true
if (hasPathSum(root.left, targetSum - root.val)) return true;
// C、如果右节点返回的是true,证明当前节点所在路径就是true,直接返回true
if (hasPathSum(root.right, targetSum - root.val)) return true;
}
}
简约版
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
if (root == null) return false;
if (root.left == null && root.right == null) return targetSum - root.val == 0;
return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val);
}
}
【113.路径总和ii】中等题
区别:与【257. 二叉树的所有路径】这道题的区别在于,257题使用字符串存储路径,字符串是不可变类型,当字符串变量作为形参,即使在函数体内修改字符串变量的值,也不会影响实参的值。但113这题,传入的是List变量,当在函数体内更改值时,会影响实参的值。因此,需要遍历完左节点之后,恢复当前节点的路径,再遍历右节点,再恢复当前节点的路径。
思路:关键是确认当前递归逻辑(路径 -> 前序遍历)
- 1、更新当前层的剩值和参数
- 2、如果当前节点为叶子节点,且当前路径符合条件,则复制当前路径并存入。
- 3、继续遍历左右节点,使用回溯恢复当前节点对应的路径
注意:当将path加入到paths时,由于List是引用类型,在Java中存储的是引用的地址。在遍历过程中,path会不断被修改并添加到paths中,但实际上paths中存储的都是同一个path对象的引用。因此,在add的时候,要利用构造器将path复制一份,再存入paths。
/**
* 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 {
List<List<Integer>> paths = new ArrayList<>(); // 存储所有路径之和=targetSum的路径
List<Integer> path = new ArrayList<>(); // 存储从根节点到当前遍历节点的路径
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
getPathSum(root, targetSum);
return paths;
}
public void getPathSum(TreeNode root, int targetSum){
if (root == null) return;
// 更新当前层的剩值和参数
targetSum = targetSum - root.val;
path.add(root.val);
// 如果当前节点为叶子节点,且当前路径符合条件,则复制当前路径并存入
// System.out.println(targetSum);
if (root.left == null && root.right == null && targetSum == 0) {
// 必须重新构建List,再存入paths,否则paths中存储的都是同一个path的引用,值相同
paths.add(new ArrayList<>(path));
}
// 继续遍历左右节点
getPathSum(root.left, targetSum);
if (root.left != null) path.remove(path.size() - 1); // 回溯
getPathSum(root.right, targetSum);
if (root.right != null) path.remove(path.size() - 1); // 回溯
}
}
【106.从中序与后序遍历序列构造二叉树】中等题
思路:
1、确定参数和返回值:利用递归构造二叉树,传入中序遍历和后序遍历数组,返回根节点
2、确定终止条件:如果后序遍历数组长度为0,证明根节点是null,直接返回null即可
3、确定单层递归逻辑:
- A. 找根节点,后序遍历(左右中)数组的最后一个元素就是根节点
- B. 根据根节点切割中序遍历数组和后序遍历数组,分别得到左右子树各自的中序遍历数组和后序遍历数组
- a. 遍历中序数组,查找root节点的val对应的索引idx
- b. 根据idx切割中序遍历数组和后序遍历数组
- - 获取左子树的中序遍历数组和后序遍历数组
- - 获取右子树的中序遍历数组和后序遍历数组
- C、递归获取根节点的左右节点
方法一 利用原来的buildTree方法实现递归(自己写的)
/**
* 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 {
// 1、确定参数和返回值:利用递归构造二叉树,传入中序遍历和后序遍历结果数组,返回头节点
public TreeNode buildTree(int[] inorder, int[] postorder) {
// 2、确定终止条件
if (postorder.length == 0) return null;
// 3、确定单层递归逻辑
// A. 找根节点:后序遍历(左右中)数组的最后一个元素就是根节点
TreeNode root = new TreeNode(postorder[postorder.length - 1]);
// System.out.println(root.val);
// B. 根据根节点切割中序遍历数组和后序遍历数组,分别得到左右子树各自的中序遍历数组和后序遍历数组
// 遍历中序数组,查找root节点的val对应的索引idx
int idx = -1;
for (int i = 0; i < inorder.length; i++) {
if (inorder[i] == root.val) {
idx = i;
break;
} // inOrder中存储的都是各异的元素,才可以找到root节点对应的真正索引
}
// 根据idx切割中序遍历数组和后序遍历数组
// - 获取左子树的中序遍历数组和后序遍历数组
int[] leftInorder = new int[idx];
int[] leftPostorder = new int[idx];
for (int i = 0; i < leftInorder.length; i++){
leftInorder[i] = inorder[i];
leftPostorder[i] = postorder[i];
}
// - 获取右子树的中序遍历数组和后序遍历数组
int[] rightInorder = new int[inorder.length - 1 - idx];
int[] rightPostorder = new int[rightInorder.length];
for (int i = 0; i < rightInorder.length; i++){
rightInorder[i] = inorder[idx + 1 + i]; // 中序的右子树需要避开右节点
rightPostorder[i] = postorder[idx + i]; //后序的右子树不需要避开root节点,root节点在最后面
}
// C. 递归传入,构造二叉树
root.left = buildTree(leftInorder, leftPostorder);
root.right = buildTree(rightInorder, rightPostorder);
return root;
}
}
缺点:需要额外O(n)的空间存储切割后的中序遍历数组和后序遍历数组,而且每次切割还需要O(n)的时间复杂度存储。
方法二 利用自定义的方法递归(优化版)
优化:
- 利用HashMap加快索引查询,key为节点的val,value为节点在中序遍历数组的索引值。
- 传入中序和后序遍历数组的开始索引和结束索引,不需要额外的数组存储。
/**
* 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 {
Map<Integer, Integer> map = new HashMap<>();
public TreeNode buildTree(int[] inorder, int[] postorder) {
// 创建哈希表,加速根节点val值的索引查找
for (int i = 0; i < inorder.length; i++){
map.put(inorder[i], i);
}
return buildTreeByIdx(inorder, 0, inorder.length, postorder, 0, postorder.length);
}
// 1、确定参数和返回值,参数传入区间和索引,要求索引对应左闭右开原则,返回二叉树的根节点
public TreeNode buildTreeByIdx(int[] inorder, int inBegin, int inEnd, int[] postorder, int postBegin, int postEnd){
// 2、确定终止条件(原则是左闭右开,如果开始和结束相等,则证明区间为空,返回null)
if (postEnd == postBegin) return null;
// 3、确定单层递归逻辑
TreeNode root = new TreeNode(postorder[postEnd - 1]);
int idx = map.get(root.val);
root.left = buildTreeByIdx(inorder, inBegin, idx,
postorder, postBegin, postBegin + idx - inBegin);
root.right = buildTreeByIdx(inorder, idx + 1, inEnd,
postorder, postBegin + idx - inBegin, postEnd - 1);
return root;
}
}
缺点:在构造左右子树时,传入的索引很容易出错,注意左子树区间的长度是idx - inBegin。
【105.从前序与中序遍历序列构造二叉树】中等题
方法 利用自定义的方法递归
思路:跟上一题【106.从中序与后序遍历序列构造二叉树】的方法二思路基本一样,区别在于该题中【根节点是前序遍历数组的第一个元素】。注意:切割的索引很容易写错。
/**
* 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 {
// 创建哈希表,加速根节点val的索引查询
Map<Integer, Integer> map = new HashMap<>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
for (int i = 0; i < inorder.length; i++){
map.put(inorder[i], i);
}
return buildTreeByIdx(preorder, 0, preorder.length, inorder, 0, inorder.length);
}
public TreeNode buildTreeByIdx(int[] preorder, int preBegin, int preEnd, int[] inorder, int inBegin, int inEnd){
// 根据左闭右开原则,当开始索引和结束索引相等时,证明前序遍历数组为空,直接返回null
if (preBegin == preEnd) return null;
// 构造根节点
TreeNode root = new TreeNode(preorder[preBegin]); // 前序遍历(中左右)的第一个节点就是根节点
// 获取根节点对应的索引
int idx = map.get(root.val);
// 按根节点的索引切割中序和后序数组,构造左子树和右子树
root.left = buildTreeByIdx(preorder, preBegin + 1, preBegin + 1 + idx - inBegin, inorder, inBegin, idx);
root.right = buildTreeByIdx(preorder, preBegin + 1 + idx - inBegin, preEnd, inorder, idx + 1, inEnd);
return root;
}
}