513.找树左下角的值
找二叉树最底层 最左边节点的值。迭代法层序遍历比较容易
注意:左下角的节点,不一定是左孩子!!只是最底层最左边的节点就行(可能是右孩子)
递归法
- 怎么找最底层?
- 只要找深度最大的叶子节点
在递归的同时记录下最大深度maxDepth
,要初始化成-1
,方便比较。用result
记录左下角节点的值。maxDepth和result都要在类里初始化!不能放在方法里!因为这两个是全局的 depth初始化成0!!!
- 确定递归函数
本题我们需要不断更新depth(初始化成0),所以放到参数里 不需要返回值了
void traversal(root, depth) {}
- 终止条件
遍历到叶子节点(左右孩子都为空),然后比较当前depth,如果大于maxDepth就更新,统计记录当前节点的数值。这样一定记录的是depth相同的节点里最左边的!(同层的不会更新result)
if(root.left == null && root.right == null) {
if(depth > maxDepth) {
maxDepth = depth;
result = root.val;
}
}
- 单层递归逻辑
如果左/右孩子不为空,就往左 往右递归,需要回溯,恢复现场 depth–
- 为什么要回溯?因为我们需要回到上一个根节点,递归回来后 depth就要回退
if(root.left != null) {
depth++;
traversal(root.left,depth);
depth--;
//上面这三行可以直接精简成 traversal(root.left,depth+1);
//因为depth的实际值并没有改变,就不用--了
}
if(root.right != null) {
...
}
-
这题为什么没有遍历 中 的逻辑?不需要啊!他路过中了,但是他判断出这是中后 就不处理中了。
-
java代码
class Solution {
private int maxDepth = -1;
private int result = 0;
public void traversal(TreeNode root, int depth) {
if(root.left == null && root.right == null) { //遇到叶子节点了
if(depth > maxDepth) {
maxDepth = depth;
result = root.val;
}
}
if(root.left != null) {
depth++;
traversal(root.left, depth);
depth--; //回溯 恢复现场
}
if(root.right != null) {
depth++;
traversal(root.right, depth);
depth--;
}
}
public int findBottomLeftValue(TreeNode root) {
int depth = 0;
traversal(root,depth);
return result;
}
}
112.路径总和
是否需要显示回溯,需要区分自己传的是值还是引用!
-
直接传targetSum进去,然后每遍历一个节点就count-=。如果到了叶子节点,同时count已经为0了,就return true;
-
==如果有一条路径满足了,我们需要一路把true返回给根节点!==所以在递归左右孩子的时候,要判断传回来的是不是true,是就一路回传。(说明左/右方向上有正确答案!)
- 确定递归函数的参数和返回类型
参数:需要二叉树的根节点,还需要一个计数器,这个计数器用来计算二叉树的一条边之和是否正好是目标和,计数器为int型。
再来看返回值,递归函数什么时候需要返回值?什么时候不需要返回值?这里总结如下三点:
- 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。
- 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。
- 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(本题的情况)
而本题我们要找一条符合条件的路径,所以递归函数需要返回值,及时返回,那么返回类型是什么呢?
bool traversal(treenode* cur, int count) // 注意函数的返回类型
- 确定终止条件
首先计数器如何统计这一条路径的和呢?
不要去累加然后判断是否等于目标和,那么代码比较麻烦,可以用递减,让计数器count初始为目标和,然后每次减去遍历路径节点上的数值。
如果最后count == 0,同时到了叶子节点的话,说明找到了目标和。
如果遍历到了叶子节点,count不为0,就是没找到。
递归终止条件代码如下:
if (!cur->left && !cur->right && count == 0) return true; // 遇到叶子节点,并且计数为0
if (!cur->left && !cur->right) return false; // 遇到叶子节点而没有找到合适的边,直接返回
- 确定单层递归的逻辑
因为终止条件是判断叶子节点,所以递归的过程中就不要让空节点进入递归了。
递归函数是有返回值的,如果递归函数返回true,说明找到了合适的路径,应该立刻返回。
代码如下:
if (cur->left) { // 左 (空节点不遍历)
// 遇到叶子节点返回true,则直接返回true
if (traversal(cur->left, count - cur->left->val)) return true; // 注意这里有回溯的逻辑
}
if (cur->right) { // 右 (空节点不遍历)
// 遇到叶子节点返回true,则直接返回true
if (traversal(cur->right, count - cur->right->val)) return true; // 注意这里有回溯的逻辑
}
return false;
class Solution {
public boolean traversal(TreeNode node,int count) {
if(node.left == null & node.right == null && count == 0) { //遇到叶子节点,且count已经为0了
return true;
}
if(node.left == null & node.right == null) return false; //遇到叶子节点了,但count不为0
if(node.left != null) {
//在递归之前先把count--
count -= node.left.val;
if(traversal(node.left, count)) return true; //要在这里判断是否左孩子是叶子节点且为true了。
count += node.left.val;
}
if(node.right != null) {
count -= node.right.val;
if(traversal(node.right, count)) return true;
count += node.right.val;
}
return false;
}
public boolean hasPathSum(TreeNode root, int targetSum) {
if(root == null) return false;
targetSum -= root.val; //传进去之前先把root的val值减掉
return traversal(root, targetSum);
}
}
113.路径总和 II
注意递归函数变成void了,参数要传node,count, result, path
- 完整代码
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) {
// 找到了和为 targetsum 的路径
if (targetsum - root.val == 0) {
res.add(new ArrayList<>(path));
}
return; // 如果和不为 targetsum,返回
}
if (root.left != null) {
preorderdfs(root.left, targetsum - root.val, res, path);
path.remove(path.size() - 1); // 回溯
}
if (root.right != null) {
preorderdfs(root.right, targetsum - root.val, res, path);
path.remove(path.size() - 1); // 回溯
}
}
}
106.从中序与后序遍历序列构造二叉树
中序:左中右
后序:左右中
这棵树的根节点一定是后序遍历序列的最后一个节点!找到根节点后,再用它 在中序序列里分割成左 和 右
- 大体逻辑:
- 切中序是用index遍历找 得到左中序,右中序
- 拿中序找到的左区间的大小 去切后序里的左区间 得到左后序,右后序
- 切割的循环不变量:左闭右开
- 左右递归是传入得到的左中序,左后序 和 右中序,右后序,分别构造左子树和右子树
- 确定递归函数
Treenode traversal(int[] inorder, int[] postorder)
- 确定终止条件
// 第一步
if (postorder.size() == 0) return NULL;
// 第二步:后序遍历数组最后一个元素,就是当前的中间节点
int rootValue = postorder[postorder.size() - 1];
TreeNode root = new TreeNode(rootValue);
// 叶子节点
if (postorder.size() == 1) return root;
- 单层递归逻辑
// 第三步:找切割点
int delimiterIndex;
for (delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex++) {
if (inorder[delimiterIndex] == rootValue) break;
}
// 第四步:切割中序数组,得到 中序左数组和中序右数组
// 第五步:切割后序数组,得到 后序左数组和后序右数组
// 第六步
root.left = traversal(中序左数组, 后序左数组);
root.right = traversal(中序右数组, 后序右数组);
return root;
- 完整代码
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) { // 不满足左闭右开,说明没有元素,返回空树
return null;
}
int rootIndex = map.get(postorder[postEnd - 1]); // 找到后序遍历的最后一个元素在中序遍历中的位置
TreeNode root = new TreeNode(inorder[rootIndex]); // 构造结点
int lenOfLeft = rootIndex - inBegin; // 保存中序左子树个数,用来确定后序数列的个数
root.left = findNode(inorder, inBegin, rootIndex,
postorder, postBegin, postBegin + lenOfLeft);
root.right = findNode(inorder, rootIndex + 1, inEnd,
postorder, postBegin + lenOfLeft, postEnd - 1);
return root;
}
}
day18总结
- 递归函数什么时候需要返回值?
- 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。
- 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。
- 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(本题的情况)
- list:
add(E)、add(int, E)、remove(int)、remove(Object)、set(int, E)、forEach()、iterator()
add(E):在末尾增加元素
add(int, E):在指定位置增加元素
remove(int):删除指定位置的元素
remove(Object):删除指定元素
set(int, E):修改指定位置的元素
forEach():for循环遍历集合
iterator():迭代器遍历元素
LinkedList特有方法:
addFirst(E e):在链表头部插入一个元素;
addLast(E e):在链表尾部添加一个元素;
push(E e):与addFirst方法一致;
offer(E e):在链表尾部插入一个元素;
removeFirst(E e):删除头,获取元素并删除;
removeLast(E e):删除尾,获取元素并删除;
pollFirst():删除头;
pollLast():删除尾;
getFirst():获取第一个元素;
getLast():获取最后一个元素;
peek():获取第一个元素,但是不移除;
pollFirst():查询并删除头;
pollLast():删除尾;
poll():查询并移除第一个元素;
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) {
// 找到了和为 targetsum 的路径
if (targetsum - root.val == 0) {
res.add(new ArrayList<>(path));
}
return; // 如果和不为 targetsum,返回
}
if (root.left != null) {
preorderdfs(root.left, targetsum - root.val, res, path);
path.remove(path.size() - 1); // 回溯
}
if (root.right != null) {
preorderdfs(root.right, targetsum - root.val, res, path);
path.remove(path.size() - 1); // 回溯
}
}
}