513.找树左下角的值
给定一个二叉树,在树的最后一行找到最左边的值。
//层序遍历
class Solution {
public int findBottomLeftValue(TreeNode root) {
Queue<TreeNode> q=new LinkedList<>();
if(root!=null) q.offer(root);
int res=0;
while(!q.isEmpty()){
int len=q.size();
for(int i=0;i<len;i++){
TreeNode node=q.poll();
if(i==0){ // 记录最后一行第一个元素
res=node.val;
}
if(node.left!=null){
q.offer(node.left);
}
if(node.right!=null){
q.offer(node.right);
}
}
}
return res;
}
递归怎么做呢
**求深度最大的叶子节点 **
左下角的值不一定是左孩子
那么如何找最左边的呢?可以使用前序遍历(当然中序,后序都可以,因为本题没有 中间节点的处理逻辑,只要左优先就行),保证优先左边搜索,然后记录深度最大的叶子节点,此时就是树的最后一行最左边的值
递归三部曲:
确定递归函数的参数和返回值
参数必须有要遍历的树的根节点,还有就是一个int型的变量用来记录最长深度。 这里就不需要返回值了,所以递归函数的返回类型为void。
本题还需要类里的两个全局变量,maxLen用来记录最大深度,result记录最大深度最左节点的数值。
代码如下:
int maxDeep = -1; //记录二叉树的最大深度
int res= 0; //记录节点里的值
void findLeftValue (TreeNode root,int deep) //这里得deep是当前记录深度
确定终止条件
当遇到叶子节点的时候,就需要统计一下最大的深度了,所以需要遇到叶子节点来更新最大深度。
代码如下:
if (root == null) return;
if (root.left == null && root.right == null) { //遍历到叶子节点
if (deep > Deep) { //统计最大深度
res = root.val;
Deep = deep;
}
}
确定单层递归的逻辑
在找最大深度的时候,递归的过程中依然要使用回溯,代码如下:
if (root.left!=null) {
depth++;
traversal(root->left, depth);
depth--; // 回溯
}
if (root.right!=null) {
depth++;
traversal(root->right, depth);
depth--; // 回溯
}
完整代码
class Solution {
int maxDeep=-1;
int res=0;
public int findBottomLeftValue(TreeNode root) {
findLeftValue(root,0);
return res;
}
public void findLeftValue(TreeNode root,int deep){
if(root==null) return;
if(root.left==null&&root.right==null){
if(deep>maxDeep){
maxDeep=deep;
res=root.val;
}
}
//处理左
if(root.left!=null){
deep++; //向左遍历 深度加一
findLeftValue(root.left,deep);
deep--; //回溯 这里的回溯是因为当前的深度就是deep,只加不减其实改变了当前的深度,而且后面需要继续操作右子树,如果改变了当前的深度,右子树的深度也会收到影响
}
//处理右
if(root.right!=null){
deep++;
findLeftValue(root.right,deep);
deep--; //回溯
}
return;
}
}
//精简版
class Solution {
int maxDeep=-1;
int res=0;
public int findBottomLeftValue(TreeNode root) {
findLeftValue(root,0);
return res;
}
public void findLeftValue(TreeNode root,int deep){
if(root==null) return;
if(root.left==null&&root.right==null){
if(deep>maxDeep){
maxDeep=deep;
res=root.val;
}
}
if(root.left!=null) findLeftValue(root.left,deep+1);
if(root.right!=null) findLeftValue(root.right,deep+1);
return;
}
}
112. 路径总和
给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。
说明: 叶子节点是指没有子节点的节点。
思路:
可以使用深度优先遍历的方式(本题前中后序都可以,无所谓,因为中节点也没有处理逻辑)来遍历二叉树
确定递归函数的参数和返回类型
参数:需要二叉树的根节点,还需要一个计数器,这个计数器用来计算二叉树的一条边之和是否正好是目标和,计数器为int型。
再来看返回值,递归函数什么时候需要返回值?什么时候不需要返回值?这里总结如下三点:
- 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。(这种情况就是本文下半部分介绍的113.路径总和ii)
- 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。 (这种情况我们在236. 二叉树的最近公共祖先中介绍)
- 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(本题的情况)
而本题我们要找一条符合条件的路径,所以递归函数需要返回值,及时返回,那么返回类型是什么呢?
如图所示:
图中可以看出,遍历的路线,并不要遍历整棵树,所以递归函数需要返回值,可以用bool类型表示。
确定终止条件
首先计数器如何统计这一条路径的和呢?
不要去累加然后判断是否等于目标和,那么代码比较麻烦,可以用递减,让计数器count初始为目标和,然后每次减去遍历路径节点上的数值。
如果最后count == 0,同时到了叶子节点的话,说明找到了目标和。
如果遍历到了叶子节点,count不为0,就是没找到。
递归终止条件代码如下:
if (cur.left==null && cur.right==null && count == 0) return true;//遇到叶子节点,并且计数为0
if (cur.left==null && cur.right==null) return false; // 遇到叶子节点直接返回
确定单层递归的逻辑
因为终止条件是判断叶子节点,所以递归的过程中就不要让空节点进入递归了。
递归函数是有返回值的,如果递归函数返回true,说明找到了合适的路径,应该立刻返回。
为了把回溯的过程体现出来,可以改为如下代码:
if (cur.left!=null) { // 左
count -= cur.left.val; // 递归,处理节点;
if (traversal(cur.left, count)) return true;
count += cur.left.val; // 回溯,撤销处理结果
}
if (cur.right!=null) { // 右
count -= cur.right.val; // 递归,处理节点;
if (traversal(cur.right, count)) return true;
count += cur.right.val; // 回溯,撤销处理结果
}
return false;
完整java代码
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
if (root == null) return false;
return traversal(root, targetSum - root.val);
}
boolean traversal(TreeNode cur, int count) {
if (cur.left==null && cur.right==null && count == 0) return true; // 遇到叶子节点,并且计数为0
if (cur.left==null && cur.right==null) return false; // 遇到叶子节点直接返回
if (cur.left!=null) { // 左
count -= cur.left.val; // 递归,处理节点;
if (traversal(cur.left, count)) return true;
count += cur.left.val; // 回溯,撤销处理结果
}
if (cur.right!=null) { // 右
count -= cur.right.val; // 递归,处理节点;
if (traversal(cur.right, count)) return true;
count += cur.right.val; // 回溯,撤销处理结果
}
return false;
}
}
//精简版 但这玩意我一下就看懂了 很离谱
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
if (root == null) return false; // 为空退出
// 叶子节点判断是否符合
if (root.left == null && root.right == null&& targetSum ==root.val) return true;
// 求两侧分支的路径和
return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val);
}
}
113. 路径总和ii
和上面那道思路差不多,但是需要返回值
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.从中序与后序遍历序列构造二叉树
根据一棵树的中序遍历与后序遍历构造二叉树。
注意: 你可以假设树中没有重复的元素。
例如,给出
- 中序遍历 inorder = [9,3,15,20,7]
- 后序遍历 postorder = [9,15,7,20,3] 返回如下的二叉树:
梦回数据结构考试
流程如图:
那么代码应该怎么写呢?
说到一层一层切割,就应该想到了递归。
来看一下一共分几步:
- 第一步:如果数组大小为零的话,说明是空节点了。
- 第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
- 第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点
- 第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)
- 第五步:切割后序数组,切成后序左数组和后序右数组
- 第六步:递归处理左区间和右区间
106.从中序与后序遍历序列构造二叉树
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;
}
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) { // 不满足左闭右开,说明没有元素,返回空树
return null;
}
int rootIndex = map.get(preorder[preBegin]); // 找到前序遍历的第一个元素在中序遍历中的位置
TreeNode root = new TreeNode(inorder[rootIndex]); // 构造结点
int lenOfLeft = rootIndex - inBegin; // 保存中序左子树个数,用来确定前序数列的个数
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;
}
}
11