左叶子之和
题目:404
- 递归的遍历顺序为后序遍历(左右中),是因为要通过递归函数的返回值来累加求取左叶子数值之和。
- 参数和返回值:一定要传入树的根节点,其返回值为数值之和
- 终止条件:空节点
- 单层递归逻辑:当遇到左叶子节点的时候,记录数值,然后通过递归求取左子树左叶子之和,和 右子树左叶子之和,相加便是整个树的左叶子之和。
找树左下角的值
题目:513
-
一眼层遍历,但是不知道哪一层是最后一层
-
无所谓,栈会出手,把每一层的最后一个加入栈里,最后栈的栈顶就是最后一层的结点
-
如果是要把栈的每一层的最后一个节点加入栈里,而且是找左节点,记得把节点加入队列的时候先加右节点再加左节点哦。是的,这里是有两个数据结构,一个辅助层遍历的队列,一个为了找答案的栈
if (len == 1) { stack.add(node); } if (node.right != null) { que.offer(node.right); } if (node.left != null) { que.offer(node.left); } -
这一题,递归有点麻烦。
-
递归函数的参数和返回值:参数要有传入的根节点还要有一个来记录最长深度,返回值void就行
-
终止条件:遇到叶子节点,统计当前深度,并与此前记录的最大深度做比较,更新最大深度的同时也更新值
-
单层递归逻辑:递归找深度的时候同时回溯。代码里把回溯精简了
`if (root.left != null) { findLeftValue(root.left, deep + 1);//每次函数调用完,deep并没有+1 } ` 其原型为 if (root.left != null) { deep++; findLeftValue(root.left, deep); deep--;//回溯 }`
路径总和
题目:112
- 两个题目对比放在一起,主要为了区分一下递归函数是否需要返回值,看有无不同
- 函数递归和参数:该题需要找到一条符合条件的路径,所以找到合适路径的时候就要返回值,根据题目,只问是否存在该路径,没问路径的具体内容,所以返回类型是布尔值。直接利用当前函数就行
- 参数的话,首先要有传入节点,然后合适的路径指节点值的和与target一样,所以还要有一个节点值和
- 终止条件:如果去不断累加然后判断,代码会比较麻烦,所以这里直接用减法,把target不断减去经过节点值,直到它为0且到了叶子节点。如果没有,那就遗憾退场
- 单层递归逻辑:就,好像也没事逻辑,减一下节点值,传入新的递归然后判断......
题目:113
-
函数递归和参数:这里需要搜索整棵二叉树,找到所有路径,所以不用处理什么递归返回值...是吧?
-
终止条件:这里依然是利用的减法,和上面的一样。先判定是否到了叶子节点,如果到了就看结果是否符合,如果没有继续递归+回溯
-
单层逻辑同理
-
其实大概看下来,112和113的总体框架类似,就是递归的那种思路啥的。113因为要的是所用路径,别的东西会多一点
-
比如需要有两个列表,一个放所有路径,一个放单个路径,所以每次递归也要带上它们。
List<List<Integer>> res = new ArrayList<>(); if (root == null) { return res; } List<Integer> path = new LinkedList<>(); preorderdfs(root, targetSum, res, path); -
每次递归,先把当前节点放进path里,然后判断
path.add(root.val);
// 遇到了叶子节点
if (root.left == null && root.right == null) {//先判断是否到了叶子节点
// 找到了和为 targetsum 的路径
if (targetsum - root.val == 0) {
res.add(new ArrayList<>(path));//把path加进答案集合里
}
return; // 如果和不为 targetsum,返回
}
- 然后是判断是否进入新的递归,同时回溯(递归和回溯一起进行),递归后跟着回溯,嗯
if (root.left != null) {
preorderdfs(root.left, targetsum - root.val, res, path);
path.remove(path.size() - 1); // 回溯
}
- 总之,这两个放在一起,可以看出找一条是否存在与找所有路径的区别,顺便也说了一下递归里关于返回值的讨论
- 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。(这种情况就是本文下半部分介绍的113.路径总和ii)
- 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。 (这种情况我们在[236. 二叉树的最近公共祖先 (opens new window)]中介绍)
- 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(本题的情况)
从中序与后序遍历序列构造二叉树
中序:左 中 右
后序:左 右 中
前序:中 左 右
题目:106 从中序和后序
- 理论上明白:用后序的最后一个元素去切割中序,这样可以知道中序里那一段表示左子树,那一段表示右子树。然后再根据中序里的顺序来切割后序,就,额,慢慢切
- 代码框架......慢慢看吧,可能一时难以消化orz
- 第一步:如果数组大小为零的话,说明是空节点了。
- 第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
- 第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点
- 第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)
- 第五步:切割后序数组,切成后序左数组和后序右数组
- 第六步:递归处理左区间和右区间
- 难点在于切割......这里参考二分法,遵循循环不变量
- 中序里切割的点好找:当前后序遍历的最后一个
- 后序里的切割:中序数组大小一定是和后序数组的大小相同的
- 中序数组我们都切成了左中序数组和右中序数组了,那么后序数组就可以按照左中序数组的大小来切割,切成左后序数组和右后序数组。
题目:105 从前序和中序
- 套路和上面一样,都可以成为一套模版,就是切割点从后序最后一位变成了前序的第一个
模版
- 首先,要有一个map用来记录中序遍历数组的数值和其位置
- 然后进入递归
- 递归函数格式:以106为例。 中序数组,中序数组begin,中序数组end,后序数组,后序数组begin,后序数组end
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]);
//找到后序遍历的最后一个元素在中序遍历中的位置(前序遍历就是map.get(preorder[preBegin])
TreeNode root = new TreeNode(inorder[rootIndex]); // 构造结点
int lenOfLeft = rootIndex - inBegin; // 保存中序左子树个数,用来确定后序数列的个数
- 新的递归:
- 左子树:分割后找到的中序遍历中的左子树,后序遍历中的左子树
- 右子树同理
root.left = findNode(inorder, inBegin, rootIndex,
postorder, postBegin, postBegin + lenOfLeft);
最大二叉树
题目:654
- 上面那个题的青春版
- 递归三步
- 确定递归函数的参数和返回值:参数传入的是存放元素的数组,返回该数组构造的二叉树的头结点。因为要构造符合条件的左子树,右子树。要确定数组里的那一部分是左子树,哪一部分是右子树,故参数还有leftIndex和rightIndex
public TreeNode constructMaximumBinaryTree1(int[] nums, int leftIndex, int rightIndex)
- 确定终止条件:那么当递归遍历的时候,如果传入的数组大小为1,说明遍历到了叶子节点了,就把数组中的这个数弄成节点返回。如果传入数组已经小于1了,说明已经到头了,开始原路返回。
if (rightIndex - leftIndex < 1) {// 没有元素了
return null;
}
if (rightIndex - leftIndex == 1) {// 只有一个元素
return new TreeNode(nums[leftIndex]);
}
- 确定单层逻辑:根据“当前数组”最大值分割。找左右子树。然后不断递归。以第一次划分为例,首先先找到数组中的最大值maxValue,并记录其索引maxIndex。然后maxIndex左边的就是它的左子树,右边就是右子树。这样确定其左子树的范围就是[leftIndex, maxIndex) (因为最初传入的leftIndex = 0, rightIndex = nums.length)] 右子树的范围就是【maxIndex+1, rightIndex)
- 之后的递归就是把视野转向刚刚划分的左右子树,去找左子树的左子树/右子树,以此类推,各种套娃。
int maxIndex = leftIndex;// 最大值所在位置
int maxVal = nums[maxIndex];// 最大值
for (int i = leftIndex + 1; i < rightIndex; i++) {
if (nums[i] > maxVal){
maxVal = nums[i];
maxIndex = i;
}
}
TreeNode root = new TreeNode(maxVal);
// 根据maxIndex划分左右子树
root.left = constructMaximumBinaryTree1(nums, leftIndex, maxIndex);
root.right = constructMaximumBinaryTree1(nums, maxIndex + 1, rightIndex);
return root;
- 以及,因为传入的是数组,要返回的是节点,所以要根据构造方法自己把数组值初始化成节点嘞