二叉树5
513 找树左下角的值
本地递归偏难,反而迭代简单属于模板题, 两种方法掌握一下
题目链接:513 找树左下角的值
文章讲解/视频讲解:513 找树左下角的值
解题思路
递归三部曲:
- 确定递归函数的参数和返回值
参数必须有要遍历的树的根节点,还有就是一个int型的变量用来记录最长深度
还需要类里的两个全局变量,maxLen用来记录最大深度,result记录最大深度最左节点的数值 - 确定终止条件
当遇到叶子节点的时候,就需要统计一下最大的深度了,所以需要遇到叶子节点来更新最大深度。 - 确定单层递归的逻辑
在找最大深度的时候,递归的过程中依然要使用回溯
前中后序遍历都可以,因为不涉及到’中’的处理,且都是先处理左子树
// 递归 前中后序遍历都可以,因为不涉及到'中'的处理,且都是先处理左子树
class Solution {
private int Deep = -1;
private int value = 0;
public int findBottomLeftValue(TreeNode root){
value = root.val;
findLeftValue(root, 0);
return value;
}
private void findLeftValue(TreeNode root, int deep){
if(root == null) return;
if(root.left == null && root.right == null){
if(deep > Deep){
value = root.val;
Deep = deep;
}
}
if(root.left != null){
deep++;
findLeftValue(root.left, deep);
deep--; // 回溯体现之处
}
if(root.right != null){
deep++;
findLeftValue(root.right, deep);
deep--; // 回溯体现之处
}
}
}
// 层序 迭代
class Solution {
// 迭代方式 借助队列
public int findBottomLeftValue(TreeNode root){
Deque<TreeNode> que = new LinkedList<>(); //辅助队列
if (root == null) {
return 0;
}
int res = 0;
que.offer(root); // 根节点入队
while (!que.isEmpty()) {
int len = que.size();
for(int i = 0; i < len; i++) {
TreeNode peek = que.poll(); //当前层元素依次出队
if(i == 0) res = peek.val; // res每次会被当前层最左边的值覆盖
// 下一层元素入队
if (peek.left != null) que.offer(peek.left);
if (peek.right != null) que.offer(peek.right);
}
}
return res;
}
}
112. 路径总和 113. 路径总和ii
本题 又一次设计要回溯的过程,而且回溯的过程隐藏的还挺深,建议先看视频来理解
题目链接:112. 路径总和
文章讲解/视频讲解:112. 路径总和
从中序与后序遍历序列构造二叉树
本题算是比较难的二叉树题目了,大家先看视频来理解。
前中后序遍历都可以,因为不涉及到’中’的处理
112 解题思路
递归三部曲
- 确定递归函数的参数和返回类型
参数:需要二叉树的根节点,还需要一个计数器,这个计数器用来计算二叉树的一条边之和是否正好是目标和,计数器为int型。
本题我们要找一条符合条件的路径,所以递归函数需要返回值,及时返回 - 确定终止条件
首先计数器如何统计这一条路径的和呢?
不要去累加然后判断是否等于目标和,那么代码比较麻烦,可以用递减,让计数器count初始为目标和,然后每次减去遍历路径节点上的数值。
如果最后count == 0,同时到了叶子节点的话,说明找到了目标和。
如果遍历到了叶子节点,count不为0,就是没找到。 - 确定单层递归的逻辑
因为终止条件是判断叶子节点,所以递归的过程中就不要让空节点进入递归了。
递归函数是有返回值的,如果递归函数返回true,说明找到了合适的路径,应该立刻返回。
113解题思路
113.路径总和ii要遍历整个树,找到所有路径,所以递归函数不要返回值!
总结
通过112. 路径总和
和 113. 路径总和ii
要明确递归函数什么时候需要返回值,什么不需要返回值。
- 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。(
113.路径总和ii
) - 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。 (
236. 二叉树的最近公共祖先
) - 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(
112.路径总和
)
// 112
// 递归+回溯
// 易懂版本
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;
// 左子树遍历
if(root.left != null){
targetSum -= root.val;
if(hasPathSum(root.left, targetSum)) return true; // 找到
targetSum += root.val; // 回溯
}
// 右子树遍历
if(root.right != null){
targetSum -= root.val;
if(hasPathSum(root.right, targetSum)) return true; // 找到
targetSum += root.val; // 回溯
}
return false;
}
}
// 112
// 递归+回溯
// 简洁版本
class solution {
public boolean haspathsum(treenode root, int targetsum) {
if (root == null) return false; // 为空退出
// 叶子节点判断是否符合
if (root.left == null && root.right == null) return root.val == targetsum;
// 求两侧分支的路径和
return haspathsum(root.left, targetsum - root.val) || haspathsum(root.right, targetsum - root.val);
}
}
// 113
// 递归+回溯
// 易懂版本
class Solution {
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
List<List<Integer>> res = new ArrayList<>();
if(root == null) return res;
List<Integer> path = new LinkedList<>();
preorderdfs(root, targetSum, res, path);
return res;
}
public void preorderdfs(TreeNode root, int targetSum, List<List<Integer>> res, List<Integer> path){
path.add(root.val);
//遍历到叶子节点
if(root.left == null && root.right == null){
if(root.val == targetSum) res.add(new ArrayList<>(path));
return; // 向上返回,继续后续查找
}
// 左
if(root.left != null){
targetSum -= root.val;
preorderdfs(root.left, targetSum, res, path);
targetSum += root.val; // 回溯
path.remove(path.size() - 1); // 回溯
}
// 右
if(root.right != null){
targetSum -= root.val;
preorderdfs(root.right, targetSum, res, path);
targetSum += root.val; // 回溯
path.remove(path.size() - 1); // 回溯
}
}
}
// 113
// 递归+回溯
// 简洁版本
class Solution {
List<List<Integer>> result;
LinkedList<Integer> path;
public List<List<Integer>> pathSum (TreeNode root,int targetSum) {
result = new LinkedList<>();
path = new LinkedList<>();
travesal(root, targetSum);
return result;
}
private void travesal(TreeNode root, int count) {
if (root == null) return;
path.offer(root.val);
count -= root.val;
if (root.left == null && root.right == null && count == 0) {
result.add(new LinkedList<>(path));
}
travesal(root.left, count);
travesal(root.right, count);
path.removeLast(); // 回溯
}
}
106.从中序与后序遍历序列构造二叉树,105.从前序与中序遍历序列构造二叉树
本题算是比较难的二叉树题目了
题目链接:106.从中序与后序遍历序列构造二叉树
文章讲解/视频讲解:106.从中序与后序遍历序列构造二叉树
解题思路
第一步:如果数组大小为零的话,说明是空节点了。
第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点
第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)(根据后序数组所确定的根节点)
第五步:切割后序数组,切成后序左数组和后序右数组(根据中序数组所确定的左右区间)
第六步:递归处理左区间和右区间
注意点:确定切割的标准,是左闭右开,还有左开右闭,还是左闭右闭,这个就是不变量,要在递归中保持这个不变量。
前序和后序不能唯一确定一棵二叉树!,因为没有中序遍历无法确定左右部分,也就是无法分割。
每层递归定义了新的数组,既耗时又耗空间,但代码是最好理解的。
用下标索引写出的代码版本,每次用下标索引来分割,这样效率会比较高。
// 106
// 递归1
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) { // 1. 已经没有元素
return null;
}
int rootIndex = map.get(postorder[postEnd - 1]); // 2. 先根据后序找到根节点 // 3. 在中序遍历中找到根节点位置
TreeNode root = new TreeNode(inorder[rootIndex]); // 构造结点
int lenOfLeft = rootIndex - inBegin; // 保存中序左子树个数,用来确定后序数列的个数
// 4.切割中序数组,确定中序遍历的左右子树
// 5. 切割后序数组,确定后序遍历的左右子树
// 6. 递归处理左右区间
root.left = findNode(inorder, inBegin, rootIndex,
postorder, postBegin, postBegin + lenOfLeft);
root.right = findNode(inorder, rootIndex + 1, inEnd,
postorder, postBegin + lenOfLeft, postEnd - 1);
return root;
}
}
// 106
// 易懂 递归2
class Solution {
public TreeNode buildTree(int[] inorder, int[] postorder) {
if(postorder.length == 0 || inorder.length == 0) return null;
return buildHelper(inorder, 0, inorder.length, postorder, 0, postorder.length); // 保持左闭右开
}
private TreeNode buildHelper(int[] inorder, int inorderStart, int inorderEnd, int[] postorder, int postorderStart, int postorderEnd){
if(postorderStart == postorderEnd) return null; // 1. 已经没有元素
int rootVal = postorder[postorderEnd - 1]; // 2. 先根据后序找到根节点
TreeNode root = new TreeNode(rootVal);
// 3. 在中序遍历中找到根节点位置
int middleIndex;
for(middleIndex = inorderStart; middleIndex < inorderEnd; middleIndex++){
if(inorder[middleIndex] == rootVal) break;
}
// 4.切割中序数组,确定中序遍历的左右子树
int leftInorderStart = inorderStart;
int leftInorderEnd = middleIndex;
int rightInorderStart = middleIndex + 1;
int rightInorderEnd = inorderEnd;
// 5. 切割后序数组,确定后序遍历的左右子树
int leftPostorderStart = postorderStart;
int leftPostorderEnd = postorderStart + (middleIndex - inorderStart);
int rightPostorderStart = leftPostorderEnd;
int rightPostorderEnd = postorderEnd - 1;
// 6. 递归处理左右区间
root.left = buildHelper(inorder, leftInorderStart, leftInorderEnd, postorder, leftPostorderStart, leftPostorderEnd);
root.right = buildHelper(inorder, rightInorderStart, rightInorderEnd, postorder, rightPostorderStart, rightPostorderEnd);
return root;
}
}
// 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保存中序序列的数值对应位置
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) { // 1. 已经没有元素
return null;
}
int rootIndex = map.get(preorder[preBegin]); // 2. 先根据后序找到根节点 // 3. 在中序遍历中找到根节点位置
TreeNode root = new TreeNode(inorder[rootIndex]); // 构造结点
int lenOfLeft = rootIndex - inBegin; // 保存中序左子树个数,用来确定后序数列的个数
// 4.切割中序数组,确定中序遍历的左右子树
// 5. 切割后序数组,确定后序遍历的左右子树
// 6. 递归处理左右区间
root.left = findNode(preorder, preBegin + 1, preBegin + lenOfLeft + 1,
inorder, inBegin, rootIndex);
root.right = findNode(preorder, preBegin + lenOfLeft + 1, preEnd,
inorder, rootIndex + 1, inEnd);
return root;
}
}