返回值,递归函数什么时候需要返回值?什么时候不需要返回值?这里总结如下三点:
- 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。(113.路径总和ii)
- 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。 (这种情况在236. 二叉树的最近公共祖先 (opens new window)中)
- 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(112. 路径总和的情况)
513.找树左下角的值
在树的最后一行找到最左边的值。
最后一行:深度最大的叶子节点一定是最后一行。
可以使用前序遍历(当然中序,后序都可以,因为本题没有 中间节点的处理逻辑,只要左优先就行),保证优先左边搜索,然后记录深度最大的叶子节点,此时就是树的最后一行最左边的值。
class Solution {
public int Depth = -1;
public int value = 0;
public int findBottomLeftValue(TreeNode root) {
value = root.val;
traversal(root , 0);
return value;
}
public void traversal(TreeNode node , int depth){
if(node == null) return;
if(node.left == null && node.right == null){ //终止条件
if(depth > Depth ) {
value = node.val;
Depth = depth;
}
}
if(node.left != null) { //左优先,先寻找最大深度的最左节点 没有再去右边找
depth++;
traversal(node.left,depth); //递归操作
depth--; //回溯操作
}
if(node.right != null){
depth++;
traversal(node.right,depth);
depth--;
}
}
}
112. 路径总和
递归函数是有返回值的,如果递归函数返回true,说明找到了合适的路径,应该立刻返回(逐层返回)。
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
if(root == null) return false;
return traversal(root , targetSum - root.val);
}
public boolean traversal(TreeNode root,int targetSum){
if(root.left == null && root.right == null && targetSum == 0) return true;
if(root.left == null && root.right == null && targetSum != 0) return false;
if(root.left != null){
targetSum -= root.left.val;
if(traversal(root.left , targetSum)) return true;
targetSum += root.left.val;
}
if(root.right != null){
targetSum -= root.right.val;
if(traversal(root.right , targetSum)) return true;
targetSum += root.right.val;
}
return false;
}
}
113. 路径总和ii
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 ArrayList<>();
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(targetSum - root.val ==0){
res.add(new ArrayList<>(path));
}
return;
}
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.从中序与后序遍历序列构造二叉树
使用递归进行切割:
-
第一步:如果后序数组大小为零的话,说明是空节点了。
-
第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
-
第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点
-
第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)
-
第五步:切割后序数组,切成后序左数组和后序右数组
-
第六步:递归处理左区间和右区间
首先要切割中序数组,为什么先切割中序数组呢?
切割点在后序数组的最后一个元素,就是用这个元素来切割中序数组的,所以必要先切割中序数组。
中序数组相对比较好切,找到切割点(后序数组的最后一个元素)在中序数组的位置,然后切割
中序数组我们都切成了左中序数组和右中序数组了,那么后序数组就可以按照左中序数组的大小来切割,切成左后序数组和右后序数组。
切割时候会产生四个区间要保证循环不变量(左闭右开 左开右闭 左闭右闭)
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);
}
public TreeNode buildHelper(int[] inorder, int inorderStart,int inorderEnd, int[] postorder,int postorderStart,int postorderEnd){
if(postorderStart == postorderEnd) return null;
int rootval = postorder[postorderEnd - 1];
TreeNode root = new TreeNode(rootval);
int middleIndex;
for ( middleIndex = inorderStart; middleIndex < inorderEnd; middleIndex++) {
if(inorder[middleIndex] == rootval){
break;
}
}
int leftinorderStart = inorderStart;
int leftinorderEnd = middleIndex;
int rightinorderStart = middleIndex + 1;
int rightinorderEnd = inorderEnd;
int leftPostorderStart = postorderStart;
int leftPostorderEnd = postorderStart + (middleIndex - inorderStart);
int rightPostorderStart = leftPostorderEnd; //左闭右开
int rightPostorderEnd = postorderEnd - 1; //最后一个为中间元素直接剔除
root.left = buildHelper(inorder,leftinorderStart,leftinorderEnd,postorder,leftPostorderStart,leftPostorderEnd);
root.right = buildHelper(inorder,rightinorderStart,rightinorderEnd,postorder,rightPostorderStart,rightPostorderEnd);
return root;
}
}
105.从前序与中序遍历序列构造二叉树
与106基本类似主要重点还是在于区间不变量(左闭右开),在于前序数组的划分(通过中序数组划分完后的长度去划分)
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder.length == 0 || inorder.length ==0)return null;
return buildHelper(preorder,0,preorder.length,inorder,0,inorder.length);
}
public TreeNode buildHelper(int[] preorder, int preorderStart,int preorderEnd, int[] inorder,int inorderStart,int inorderEnd){
if(preorderStart == preorderEnd) return null;
int rootval = preorder[preorderStart];
TreeNode root = new TreeNode(rootval);
int middleIndex;
for ( middleIndex = inorderStart; middleIndex < inorderEnd; middleIndex++) {
if(inorder[middleIndex] == rootval){
break;
}
}
int leftinorderStart = inorderStart;
int leftinorderEnd = middleIndex;
int rightinorderStart = middleIndex + 1;
int rightinorderEnd = inorderEnd;
int leftpreorderStart = preorderStart + 1;
int leftpreorderEnd = leftpreorderStart + middleIndex - inorderStart ;
int rightpreorderStart = leftpreorderEnd ;
int rightpreorderEnd = preorderEnd;
root.left = buildHelper(preorder,leftpreorderStart,leftpreorderEnd,inorder,leftinorderStart,leftinorderEnd);
root.right = buildHelper(preorder,rightpreorderStart,rightpreorderEnd,inorder,rightinorderStart,rightinorderEnd);
return root;
}
}