题目链接:513. 找树左下角的值 - 力扣(LeetCode)
作者思考:
找树左下角的值,左下角不一定就是左子树,它是这个树最下面一层的最左边的叶子结点。那么既然这样最想想到的就是层序遍历,这题用层序遍历也是最简单、最容易理解的。获取每层的第一个元素,如果这个元素在最后一层就返回它。完整代码
class Solution {
public int findBottomLeftValue(TreeNode root) {
Queue<TreeNode> que = new LinkedList<>();
que.add(root);
int max = -1;
while (!que.isEmpty()) {
int size = que.size();
for (int i = 0; i < size; i++) {
TreeNode node = que.peek();
que.poll();
if (i == 0) {
max = node.val;
}
if (node.left != null) {
que.add(node.left);
}
if (node.right != null) {
que.add(node.right);
}
}
}
return max;
}
}
那这题我们想想可不可以用递归的方法做呢?
每次写递归前,一定要在心中想一下递归三部曲:确定参数和返回值、确定递归的终止条件、确定单层递归的逻辑。逐步进行求解。
那么本题并没有对结点的位置有要求,那么我们采用前中后三种遍历都是可以的。(本题采用的是前序遍历)。参数需要TreeNode root,int result(记录叶子结点的值), int depth(因为和层序遍历不同,递归法并不知道到自己当前处于第几层,所以需要一个遍历进行记录对比)
终止条件:当左右孩子都为空,也就是叶子结点时
if (root.left == null && root.right == null) {//遇到叶子结点
if (depth > maxDepth) {
maxDepth = depth;
result = root.val;
}
return;
}
单层递归逻辑(拿左孩子举例)
当左孩子不为空的时候,深度加1,进行递归逻辑;当退出本层的递归后,需把深度返回上一层的深度,这就是回溯的过程。
if (root.left != null) {
depth++;//每次向下遍历一层深度加1
traversal(root.left, depth);
depth--;//回溯的过程
}
完整代码
class Solution {
int maxDepth = -1;//记录全局中的最大深度
int result = 0;//记录叶子结点的值
public int findBottomLeftValue(TreeNode root) {
traversal(root, 1);
return result;
}
public void traversal(TreeNode root, int depth) {
//对结点的顺序没有要求 故前序遍历
if (root.left == null && root.right == null) {//遇到叶子结点
if (depth > maxDepth) {
maxDepth = depth;
result = root.val;
}
return;
}
if (root.left != null) {
depth++;//每次向下遍历一层深度加1
traversal(root.left, depth);
depth--;//回溯的过程
}
if (root.right != null) {
depth++;
traversal(root.right, depth);
depth--;
}
}
}
题目链接:112. 路径总和 - 力扣(LeetCode)
作者思考:
之前做过二叉树中所有的路径。本题对节点的顺序没有太大要求,前中后序的遍历都可,本题使用的递归中的前序遍历。递归开始前,还是先想一下递归三部曲,再开始做题。确定参数和返回值、确定递归的终止条件、确定单层递归的逻辑。逐步进行求解。本题不需要我们对具体的路径返回,故我们不需要path参数,只需要题目所给的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;
}
单层递归逻辑:
正常思路,从根节点向下遍历时,每遍历一个结点就加一个结点,最后遍历到叶子结点时进行比较。虽然可以,但是需要多定义一个参数,最后才能与目标值进行比较;(优化)可以将目标值进行累减操作,最后遍历到叶子结点,判断是否为0即可。当跳出递归后,进行一个回溯操作。
完整代码
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;
boolean left = traversal(root.left, targetSum);
if (left) {
return true;
}
targetSum += root.left.val;
}
if (root.right != null) {
targetSum -= root.right.val;
boolean right = traversal(root.right, targetSum);
if (right) {
return true;
}
targetSum += root.right.val;
}
return false;
}
}
题目链接:113. 路径总和 II - 力扣(LeetCode)
这题的思路和上一题基本一样,这题就是要把符合条件的路径输出出来而已,多了一层回溯。完整代码
class Solution {
List<List<Integer>> result = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
if (root == null) {
return result;
}
List<Integer> path = new ArrayList<>();
path.add(root.val);
traversal(root, targetSum-root.val, path);
return result;
}
public void traversal(TreeNode root, int targetSum, List<Integer> path) {
//确定递归终点
if (root.left == null & root.right == null && targetSum == 0) {
//这里的path为引用类型,直接加入的话始终是最初的List
result.add(new ArrayList<>(path));
return ;
}
if (root.left == null && root.right == null && targetSum != 0) {
return ;
}
if (root.left != null) {//左
targetSum -= root.left.val;
path.add(root.left.val);
traversal(root.left, targetSum, path);
path.remove(path.size() -1);//回溯
targetSum += root.left.val;//回溯
}
if (root.right != null) {//右
targetSum -= root.right.val;
path.add(root.right.val);
traversal(root.right, targetSum, path);
path.remove(path.size() -1);//回溯
targetSum += root.right.val;
}
}
}
题目链接:106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode)
作者思考:
当已知一个二叉树的中序遍历和后序遍历就可以确定唯一一个二叉树,因为中序遍历可以将二叉树的左右孩子分开,后序遍历可以直到每个左右孩子的双亲结点。
我们可以将递归分成下面几步:
①如果数组大小为零的话,说明是空节点
②如果不为空,那么取后序数组最后一个元素作为结点元素
③找到后序数组最后一个元素在中序数组的位置,作为切割点
④切割中序数组,切成中序左数组和中序右数组
⑤切割后序数组,切成后序左数组和后序右数组
⑥递归处理左区间和右区间
注意点:从后序数组中找到的切割点,一定要先切中序数组,因为中序数组的遍历顺序是左--中--右,找到的这个切割点就是中,它把左右子数清晰的分开,这样才能进行下一次切割。
class Solution {
public TreeNode buildTree(int[] inorder, int[] postorder) {
if (inorder.length == 0 || postorder.length == 0) {
return null;
}
if (postorder.length == 0) {
return null;
}
TreeNode node = new TreeNode(postorder[postorder.length -1]);//将后序遍历中最后一个结点传入树中
if (postorder.length == 1) {//叶子结点
return node;
}
//从中序遍历中寻找切割点
int index;
for (index = 0; index < inorder.length; index++) {
//找到切割点,将中序遍历的树分割为左右子树
if (inorder[index] == postorder[postorder.length -1]) {
break;
}
}
//分割中序遍历
int[] leftInorder = Arrays.copyOfRange(inorder, 0, 0+index);
int[] rightInorder = Arrays.copyOfRange(inorder, 0+index+1, inorder.length);
//分割后序遍历
int[] leftPostorder = Arrays.copyOfRange(postorder, 0, 0+index);
int[] rightPostorder = Arrays.copyOfRange(postorder, 0+index, postorder.length-1);
node.left = buildTree(leftInorder, leftPostorder);
node.right = buildTree(rightInorder, rightPostorder);
return node;
}
}