力扣题目链接
给定一个二叉树的 根节点 root
,请找出该二叉树的 最底层 最左边 节点的值。
假设二叉树中至少有一个节点。
示例 1:
输入: root = [2,1,3]
输出: 1
示例 2:
输入: [1,2,3,4,null,5,6,null,null,7]
输出: 7
深度最大的叶子节点一定是最后一行,那么如果找最左边的呢?可以使用前序遍历(当然中序,后序都可以,因为本题没有 中间节点的处理逻辑,只要左优先就行),保证优先左边搜索,然后记录深度最大的叶子节点,此时就是树的最后一行最左边的值。
递归三要素:
本题还需要类里的两个全局变量,maxDepth用来记录最大深度,result记录最大深度最左节点的数值。
// 定义全局变量
public int maxDepth = Integer.MIN_VALUE;
// 记录叶子节点最左左边的数值
public int res;
public void traversal(TreeNode root, int depth)
// 找到叶子节点
if (root.left == null && root.right == null) {
if (depth > maxDepth) {
maxDepth = depth;
res = root.val;
}
}
当找到叶子节点时,再去找其他叶子时就要回溯深度
// 如果不是叶子节点就往下,先左后右
if (root.left != null) {
depth++;
traversal(root.left, depth);
depth--; //回溯
}
if (root.right != null) {
depth++;
traversal(root.right, depth);
depth--; //回溯
}
用递归的话就就一直向左遍历,最后一个就是答案吗?
一直向左遍历到最后一个,未必是最后一行。记住最底层 最左边的节点不一定是左孩子节点,也有可能是右孩子节点,但是我们最后要遍历的顺序是要保证先找左再找右
完整代码:
public class FindBottomLeftValue {
// 递归--------------------------------------------------------------
// 定义全局变量
public int maxDepth = Integer.MIN_VALUE;
// 记录叶子节点最左左边的数值
public int res;
public int findBottomLeftValue(TreeNode root) {
traversal(root, 0);
return res;
}
public void traversal(TreeNode root, int depth) { // depth记录当前遍历的深度
// 找到叶子节点
if (root.left == null && root.right == null) {
if (depth > maxDepth) {
maxDepth = depth;
res = root.val;
}
}
// 如果不是叶子节点就往下,先左后右
if (root.left != null) {
depth++;
traversal(root.left, depth);
depth--; //回溯
}
if (root.right != null) {
depth++;
traversal(root.right, depth);
depth--; //回溯
}
}
}
// 递归--------------------------------------------------------------
层序遍历,每行子树先左后右存进队列,记录每行的第一个值
// 迭代--------------------------------------------------------------
public int findBottomLeftValue1(TreeNode root) {
Deque<TreeNode> que = new ArrayDeque<>();
if (root != null) {
que.offer(root);
}
int result = 0;
while (!que.isEmpty()) {
int size = que.size();
for (int i = 0; i < size; i++) {
TreeNode node = que.poll();
if (i == 0) {
result = node.val;
}
if (node.left != null) {
que.offer(node.left);
}
if (node.right != null) {
que.offer(node.right);
}
}
}
return result;
}
// 迭代--------------------------------------------------------------
力扣题目链接
给你二叉树的根节点 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
解释:由于树是空的,所以不存在根节点到叶子节点的路径。
可以使用深度优先遍历的方式(本题前中后序都可以,无所谓,因为中节点也没有处理逻辑)来遍历二叉树
递归三要素:
public boolean traversal(TreeNode root, int targetSum)
如果最后count == 0,同时到了叶子节点的话,说明找到了目标和。
如果遍历到了叶子节点,count不为0,就是没找到。
// 最终停止条件
if (root.left == null && root.right == null && targetSum == 0) { // 此时和目标值
return true;
}
if (root.left == null && root.right == null && targetSum != 0) { // 此时和不为目标值
return false;
}
// 不是叶子就让下遍历
if (root.left != null) {
// 先减去当前值
targetSum = targetSum - root.left.val;
if (traversal(root.left, targetSum)) {
return true;
}
// 回溯要把当前的值加上
targetSum = targetSum + root.left.val;
}
if (root.right != null) {
// 先减去当前值
targetSum = targetSum - root.right.val;
if (traversal(root.right, targetSum)) {
return true;
}
// 回溯要把当前的值加上
targetSum = targetSum + root.right.val;
}
return false;
题目也要回溯过程,因为每次往下递归就会让target减去当前节点值,最后路径不符合条件return后还要把当前节点的值加到target上
完整代码:
public boolean hasPathSum(TreeNode root, int targetSum) {
if (root == null) {
return false;
}
return traversal(root, targetSum - root.val);
}
public boolean traversal(TreeNode root, int targetSum){
// 最终停止条件
if (root.left == null && root.right == null && targetSum == 0) { // 此时和目标值
return true;
}
if (root.left == null && root.right == null && targetSum != 0) { // 此时和不为目标值
return false;
}
// 不是叶子就让下遍历
if (root.left != null) {
// 先减去当前值
targetSum = targetSum - root.left.val;
if (traversal(root.left, targetSum)) {
return true;
}
// 回溯要把当前的值加上
targetSum = targetSum + root.left.val;
}
if (root.right != null) {
// 先减去当前值
targetSum = targetSum - root.right.val;
if (traversal(root.right, targetSum)) {
return true;
}
// 回溯要把当前的值加上
targetSum = targetSum + root.right.val;
}
return false;
}
力扣题目链接
给你二叉树的根节点 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
输出:[]
与112. 路径总和同理
因为只用找路径,所以递归函数不要返回值
注意:用一个数组保存结果,一个数组保存路径,一棵树可能有不止一条符合条件的路径,每次找到不符合要求的路径要进行回溯,既要回溯target减去的节点的值,也要回溯加入path的路径
new ArrayList<>(path)
的话,放多少个path都相当于是一个的地址完整代码:
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
if (root == null) {
return res;
}
// 根节点不为null就加入
path.add(root.val);
traversal(root, targetSum - root.val, res, path);
return res;
}
public void traversal(TreeNode root, int targetSum, List<List<Integer>> res, List<Integer> path) {
// 遇到叶子节点
if (root.left == null && root.right == null && targetSum == 0) {
res.add(new ArrayList<>(path)); //此时加入的必须要new
path.clear();
return;
}
if (root.left == null && root.right == null && targetSum != 0) {
return;
}
//没有遇到叶子节点往下遍历
if (root.left != null) {
// 节点不为null就加入path
path.add(root.left.val);
targetSum -= root.left.val;
traversal(root.left, targetSum, res, path);
// 回溯加上值且删掉加入path的元素
targetSum += root.left.val;
path.remove(path.size()-1);
}
if (root.right != null) {
// 节点不为null就加入path
path.add(root.right.val);
targetSum -= root.right.val;
traversal(root.right, targetSum, res, path);
// 回溯加上值且删掉加入path的元素
targetSum += root.right.val;
path.remove(path.size()-1);
}
return;
}
力扣题目链接
给定两个整数数组 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]
以后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来在切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。
一层一层切割,用递归一共分6步:
用后序数组的最后一个元素在中序数组的位置index为界限来切分
用切分过的中序数组的左数组的大小来切后序数组,因为切分的中序的左区间一定和后续的左区间相同(就是中序数组大小一定是和后序数组的大小相同的)
public TreeNode buildTree(int[] inorder, int[] postorder) {
// 如果数组大小为0,那么肯定组成不了树
if (inorder.length == 0 || postorder.length == 0) return null;
return traversal(inorder, postorder, 0, inorder.length, 0, postorder.length);
}
// 中序区间:[inorderBegin, inorderEnd),后序区间[postorderBegin, postorderEnd)
public TreeNode traversal(int[] inorder, int[] postorder, int inorderBegin, int inorderEnd, int postorderBegin, int postorderEnd) {
// 第一步:如果数组大小为零的话,说明是空节点了。
if (postorderBegin == postorderEnd) { //左开右闭,相等则len为0
return null;
}
// 第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
int rootValue = postorder[postorderEnd - 1];
// 建树
TreeNode root = new TreeNode(rootValue);
// 当后序数组只有一个元素那就是叶子节点
if (postorderEnd - postorderBegin == 1) {
return root;
}
// 第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点
int cutIndex;
for (cutIndex = inorderBegin; cutIndex < inorderEnd; cutIndex++) {
// 找到与root相同元素
if (inorder[cutIndex] == rootValue) break;
}
// 第四步:切割中序数组,切成中序左数组和中序右数组
// 左中序区间,左闭右开[leftInorderBegin, leftInorderEnd)
int leftInorderBegin = inorderBegin;
int leftInorderEnd = cutIndex;
// 右中序区间,左闭右开[rightInorderBegin, rightInorderEnd)
int rightInorderBegin = cutIndex + 1;
int rightInorderEnd = inorderEnd;
// 第五步:切割后序数组,切成后序左数组和后序右数组
// 左后序区间,左闭右开[leftPostorderBegin, leftPostorderEnd)
int leftPostorderBegin = postorderBegin;
int leftPostorderEnd = postorderBegin + (cutIndex - inorderBegin); // 终止位置是 需要加上 中序区间的大小size
// 右后序区间,左闭右开[rightPostorderBegin, rightPostorderEnd)
int rightPostorderBegin = postorderBegin + (cutIndex - inorderBegin);
int rightPostorderEnd = postorderEnd - 1; // 排除最后一个元素,已经作为节点了
// 第六步:递归处理左区间和右区间
root.left = traversal(inorder, postorder, leftInorderBegin, leftInorderEnd, leftPostorderBegin, leftPostorderEnd);
root.right = traversal(inorder, postorder, rightInorderBegin, rightInorderEnd, rightPostorderBegin, rightPostorderEnd);
return root;
}
力扣题目地址
给定两个整数数组 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]
和106.从中序与后序遍历序列构造二叉树一样的思路
此时前序数组的第一个元素就是根节点,再将根节点进去中序数组切分,然后中序数组的左数组切分前序数组除了第一位以后的数组
完整代码:
public TreeNode buildTree(int[] preorder, int[] inorder) {
// 如果数组大小为0,那么肯定组成不了树
if (inorder.length == 0 || preorder.length == 0) return null;
return traversal(inorder, preorder, 0, inorder.length, 0, preorder.length);
}
// 中序区间:[inorderBegin, inorderEnd),后序区间[preorderBegin, preorderEnd)
public TreeNode traversal(int[] inorder, int[] preorder, int inorderBegin, int inorderEnd, int preorderBegin, int preorderEnd) {
// 第一步:如果数组大小为零的话,说明是空节点了。
if (preorderBegin == preorderEnd) { //左开右闭,相等则len为0
return null;
}
// 第二步:如果不为空,那么取前序数组第一个元素作为节点元素。
int rootValue = preorder[preorderBegin];
// 建树
TreeNode root = new TreeNode(rootValue);
// 当前序数组只有一个元素那就是叶子节点
if (preorderEnd - preorderBegin == 1) {
return root;
}
// 第三步:找到前序数组最后一个元素在中序数组的位置,作为切割点
int cutIndex;
for (cutIndex = inorderBegin; cutIndex < inorderEnd; cutIndex++) {
// 找到与root相同元素
if (inorder[cutIndex] == rootValue) break;
}
// 第四步:切割中序数组,切成中序左数组和中序右数组
// 左中序区间,左闭右开[leftInorderBegin, leftInorderEnd)
int leftInorderBegin = inorderBegin;
int leftInorderEnd = cutIndex;
// 右中序区间,左闭右开[rightInorderBegin, rightInorderEnd)
int rightInorderBegin = cutIndex + 1;
int rightInorderEnd = inorderEnd;
// 第五步:切割前序数组,切成前序左数组和前序右数组
// 左前序区间,左闭右开[leftPreorderBegin, leftPreorderEnd)
int leftPreorderBegin = preorderBegin + 1;
int leftPreorderEnd = preorderBegin + 1 + (cutIndex - inorderBegin); // 终止位置是 需要加上 中序区间的大小size
// 右前序区间,左闭右开[rightPreorderBegin, rightPreorderEnd)
int rightPreorderBegin = preorderBegin + 1 + (cutIndex - inorderBegin);
int rightPreorderEnd = preorderEnd; // 排除最后一个元素,已经作为节点了
// 第六步:递归处理左区间和右区间
root.left = traversal(inorder, preorder, leftInorderBegin, leftInorderEnd, leftPreorderBegin, leftPreorderEnd);
root.right = traversal(inorder, preorder, rightInorderBegin, rightInorderEnd, rightPreorderBegin, rightPreorderEnd);
return root;
}