1. 二叉树遍历总结
1.1 什么时候需要用回溯?
把进入左子树改变的变量(如为了更新参数而改变的变量值、函数内改变的变量值),还原到改变前,使得右子树的计算不受影响
彻底搞懂dfs与回溯
【DFS笔记】什么时候需要回溯?什么时候不需要回溯?
DFS的本质是每一步做选择,当这个选择可做可不做(比如迷宫,这一步可以走,也可以不走),要在递归之后回溯,如迷宫问题
当遇到一个选择时,一定要对它操作时,那么就不需要回溯。比如要标记求所有情况,找到了就要标记。如果取消标记,那么就会重复计算,例:求连通块的个数
求深度可以从上到下去查 所以需要前序遍历(中左右)
而高度只能从下到上去查,所以只能后序遍历(左右中)
求最大深度:回溯VS不回溯
自底向上:(后序遍历),对于树中的任意一个节点,如果知道它子节点的答案(0),你能计算出该节点的答案(max+1)
//计算最大深度的例子:计算高度
//递归法1:后序遍历,自底向上
class Solution {
public int maxDepth(TreeNode root) {//参数
if(root == null) {return 0;} //终止条件
//单层逻辑:最底部树
int leftMax = maxDepth(root.left);
int rightMax= maxDepth(root.right);
int max = 1 + Math.max(leftMax, rightMax);
return max;
}
**自顶向下:(前序遍历)**递归+回溯,使用这些参数和节点本身的值来决定什么应该是传递给它子节点的参数(dep+1)
//计算最大深度的例子
class Solution {
//递归法2:前序,代码如下:(充分表现出求深度回溯的过程)自顶向下
int max = 0;
public int maxDepth(TreeNode root) {//参数
if(root == null) {return 0;} //终止条件
maxD(root, 1);
return max;
}
public void maxD(TreeNode root,int dep){
if(root.left == null && root.right == null) {
max = Math.max(dep, max);
return;
} //终止条件
//单层逻辑:顶部树
if(root.left != null){
dep++;
maxD(root.left, dep);
dep--;
}
if(root.right != null){
dep++;
maxD(root.right, dep);
// dep--; 这个最后没有实际作用,只是保持对称
}
}
}
但求最小深度时,后序遍历还需要处理“左右孩子不为空的逻辑”,似乎两个方法不通用,因此写前序遍历求最大最小深度逻辑上最容易想通
前序遍历题目:求树深度(自顶往下)、二叉树的所有路径、二叉树路径总和、完全二叉树的节点个数、找树左下角的值(因为要求最大深度)
后序遍历题目:求树高度(自底往上)、平衡二叉树判断(每个节点左右子树高度)、左叶子之和、平衡二叉树(两个子树高度比较)、
1.2 什么时候递归函数需要返回值
返回值主要看终止条件或者单层逻辑中,中间结果是否需要被用到
- 搜索一半就停:二叉树路径总和,如果中间结果满足条件就在终止条件中return
- 搜索整棵、需要处理;
- 搜索整棵、且不需要处理:在二叉树路径总和Ⅱ中,直接将当前结果add到全局变量中即可
2.树左下角的值
2.1 链接
https://leetcode.cn/problems/find-bottom-left-tree-value
2.2 关键知识点
- 求最底层利用前序遍历记录当前深度;
- 求最左边利用 前序遍历中 保证 先遍历左边,保证第一个大于的为最左边
2.3 自己遇到的细节问题
2.4 题解
/**
* 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 maxDep = 0;
int maxValue = 0;
public int findBottomLeftValue(TreeNode root) {
maxLeft(root, 1);
return maxValue;
}
public void maxLeft(TreeNode root, int dep){
if(root == null){return;}
if(root.left == null && root.right == null){
if(dep > maxDep){ //这里控制得到的最左边
maxDep = Math.max(maxDep, dep);
maxValue = root.val;
}
}
if(root.left != null){
maxLeft(root.left, dep + 1);
}
if(root.right != null){
maxLeft(root.right, dep + 1);
}
}
}
3.路径总和
3.1 链接
https://leetcode.cn/problems/path-sum
3.2 关键知识点
- 注意这道题一定需要有返回值,与完全遍历完全部没找到区分来开
3.3 自己遇到的细节问题
- 整体是不断减去root.val的直到为0的过程
3.4 题解
/**
* 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;}
if(root.left == null && root.right == null && (targetSum - root.val) == 0){
return true;
}
boolean boolLeft = false;
boolean boolRight = false;
if(root.left != null){
boolLeft = hasPathSum(root.left, targetSum - root.val);
}
if(root.right != null){
boolRight = hasPathSum(root.right, targetSum - root.val);
}
return (boolLeft || boolRight);
}
}
4.路径总和II 输出路径
3.1 链接
https://leetcode.cn/problems/path-sum-ii
3.2 关键知识点
3.3 自己遇到的细节问题
- 如何深拷贝一个ArrayList:直接赋值是浅拷贝,而 new ArrayList<>(as) 是深拷贝
3.4 题解
/**
* 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>> rs = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
sumPath(root, targetSum,new ArrayList<>());
return rs;
}
public void sumPath(TreeNode root, int targetSum, List<Integer> path){//参数包括root和中间变量:当前和、当前路径list
if(root == null){return;}
path.add(root.val);
if(root.left == null && root.right == null && targetSum - root.val == 0){
rs.add(path);
}
if(root.left != null){
sumPath(root.left, targetSum - root.val, new ArrayList<>(path));//深拷贝path避免right计算受影响
}
if(root.right != null){
sumPath(root.right, targetSum - root.val, new ArrayList<>(path));
}
}
}
5.根据中序遍历和后序遍历生成树
5.1 链接
https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal
5.2 关键知识点
- 递归终止条件为到叶子节点,没必要再分割了
- 所有的区间定义全部遵循前闭后开
- 后序遍历+中序遍历的逻辑,两个遍历数组切割时分别跳过的是哪里
- 理解这种在数组的各个段操作的方法
5.3 自己遇到的细节问题
- map的put方法,放入键值对 map.put(k,v)
- 最开始写的终止条件并不完全是为真情况的反,inBegin >= inEnd才是
5.4 题解
/**
* 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) {
for(int i = 0; i < inorder.length; i ++){
map.put(inorder[i], i);
}
TreeNode root = buildTreeByInPost(inorder, 0, inorder.length, postorder, 0, postorder.length);
return root;
}
public TreeNode buildTreeByInPost(int[] inorder,int inBegin, int inEnd, int[] postorder,int postBegin,int postEnd){
//终止条件:区间值包含一个值——叶子节点
if(inBegin >= inEnd){ return null;}
//单层逻辑
//1后序遍历尾
int postRoot = postorder[postEnd - 1];
//根据后序遍历尾确定中
TreeNode root = new TreeNode(postRoot);
int idx = map.get(postRoot);
// 1. 前闭后开 2 需要跳过idx 3. 两个序列长度一致,定中写后
root.left = buildTreeByInPost(inorder, inBegin, idx, postorder, postBegin, postBegin + (idx - inBegin));
root.right = buildTreeByInPost(inorder, idx + 1, inEnd, postorder, postBegin + (idx - inBegin),postEnd - 1);//由于后序后面肯定是-1,因此通过长度定或者直接-1都行
// root.right = buildTreeByInPost(inorder, idx + 1, inEnd, postorder, postBegin + (idx - inBegin),postBegin + (idx - inBegin) + (inEnd - (idx + 1)));
return root;
}
}