力扣刷题day16|513找树左下角的值、112路径总和、113路径总和II、106从中序与后序遍历序列构造二叉树、105.从前序与中序遍历序列构造二叉树

文章目录

    • 513. 找树左下角的值
      • 递归思路
        • 难点
      • 层序遍历迭代
    • 112. 路径总和
      • 递归思路
    • 113. 路径总和II
      • 递归思路
        • 难点
    • 106. 从中序与后序遍历序列构造二叉树
      • 思路
        • 难点
    • 105. 从前序与中序遍历序列构造二叉树
      • 思路

513. 找树左下角的值

力扣题目链接

给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。

假设二叉树中至少有一个节点。

示例 1:

输入: root = [2,1,3]
输出: 1

示例 2:

输入: [1,2,3,4,null,5,6,null,null,7]
输出: 7

递归思路

深度最大的叶子节点一定是最后一行,那么如果找最左边的呢?可以使用前序遍历(当然中序,后序都可以,因为本题没有 中间节点的处理逻辑,只要左优先就行),保证优先左边搜索,然后记录深度最大的叶子节点,此时就是树的最后一行最左边的值。

递归三要素:

  1. 确定递归函数的参数和返回值:参数必须有要遍历的树的根节点,还有就是一个int型的变量用来记录最长深度。 这里就不需要返回值了,所以递归函数的返回类型为void。

本题还需要类里的两个全局变量,maxDepth用来记录最大深度,result记录最大深度最左节点的数值。

// 定义全局变量
public int maxDepth = Integer.MIN_VALUE;
// 记录叶子节点最左左边的数值
public int res;
public void traversal(TreeNode root, int depth)
  1. 确定终止条件:当遇到叶子节点的时候,就需要统计最大的深度,但是当前的可能不是最大深度的叶子,所以需要遇到叶子节点来更新最大深度。
// 找到叶子节点
if (root.left == null && root.right == null) {
    if (depth > maxDepth) {
        maxDepth = depth;
        res = root.val;
    }
}
  1. 确定单层递归的逻辑:在找最大深度的时候,递归的过程中依然要使用回溯,如图所示

力扣刷题day16|513找树左下角的值、112路径总和、113路径总和II、106从中序与后序遍历序列构造二叉树、105.从前序与中序遍历序列构造二叉树_第1张图片

当找到叶子节点时,再去找其他叶子时就要回溯深度

// 如果不是叶子节点就往下,先左后右
if (root.left != null) {
    depth++;
    traversal(root.left, depth);
    depth--; //回溯
}
if (root.right != null) {
    depth++;
    traversal(root.right, depth);
    depth--; //回溯
}

难点

用递归的话就就一直向左遍历,最后一个就是答案吗?

一直向左遍历到最后一个,未必是最后一行。记住最底层 最左边的节点不一定是左孩子节点,也有可能是右孩子节点,但是我们最后要遍历的顺序是要保证先找左再找右

完整代码:

public class FindBottomLeftValue {
    // 递归--------------------------------------------------------------
    // 定义全局变量
    public int maxDepth = Integer.MIN_VALUE;
    // 记录叶子节点最左左边的数值
    public int res;

    public int findBottomLeftValue(TreeNode root) {
        traversal(root, 0);
        return res;
    }

    public void traversal(TreeNode root, int depth) { // depth记录当前遍历的深度
        // 找到叶子节点
        if (root.left == null && root.right == null) {
            if (depth > maxDepth) {
                maxDepth = depth;
                res = root.val;
            }
        }
        // 如果不是叶子节点就往下,先左后右
        if (root.left != null) {
            depth++;
            traversal(root.left, depth);
            depth--; //回溯
        }
        if (root.right != null) {
            depth++;
            traversal(root.right, depth);
            depth--; //回溯
        }

    }
}
    // 递归--------------------------------------------------------------

层序遍历迭代

层序遍历,每行子树先左后右存进队列,记录每行的第一个值

// 迭代--------------------------------------------------------------
public int findBottomLeftValue1(TreeNode root) {
    Deque<TreeNode> que = new ArrayDeque<>();
    if (root != null) {
        que.offer(root);
    }
    int result = 0;
    while (!que.isEmpty()) {
        int size = que.size();

        for (int i = 0; i < size; i++) {
            TreeNode node = que.poll();
            if (i == 0) {
                result = node.val;
            }
            if (node.left != null) {
                que.offer(node.left);
            }
            if (node.right != null) {
                que.offer(node.right);
            }
        }
    }
    return result;
}

// 迭代--------------------------------------------------------------

112. 路径总和

力扣题目链接

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false

叶子节点 是指没有子节点的节点。

示例 1:

输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true
解释:等于目标和的根节点到叶节点路径如上图所示。

示例 2:

输入:root = [1,2,3], targetSum = 5
输出:false
解释:树中存在两条根节点到叶子节点的路径:
(1 --> 2): 和为 3
(1 --> 3): 和为 4
不存在 sum = 5 的根节点到叶子节点的路径。

示例 3:

输入:root = [], targetSum = 0
输出:false
解释:由于树是空的,所以不存在根节点到叶子节点的路径。

递归思路

可以使用深度优先遍历的方式(本题前中后序都可以,无所谓,因为中节点也没有处理逻辑)来遍历二叉树

递归三要素:

  1. 确定递归函数的参数和返回类型:参数:需要二叉树的根节点,还需要一个计数器,这个计数器用来计算二叉树的一条边之和是否正好是目标和,计数器为int型。遍历的路线,并不要遍历整棵树,要判断是否有符合条件的路径,所以返回布尔类型
public boolean traversal(TreeNode root, int targetSum)
  1. 确定终止条件:不要去累加然后判断是否等于目标和,那么代码比较麻烦,可以用递减,让计数器count初始为目标和,然后每次减去遍历路径节点上的数值。
  • 如果最后count == 0,同时到了叶子节点的话,说明找到了目标和。

  • 如果遍历到了叶子节点,count不为0,就是没找到。

// 最终停止条件
if (root.left == null && root.right == null && targetSum == 0) { // 此时和目标值
    return true;
}
if (root.left == null && root.right == null && targetSum != 0) { // 此时和不为目标值
    return false;
}
  1. 确定单层递归的逻辑:因为终止条件是判断叶子节点,所以递归的过程中就不要让空节点进入递归了。递归函数是有返回值的,如果递归函数返回true,说明找到了合适的路径,应该立刻返回。
// 不是叶子就让下遍历
if (root.left != null) {
    // 先减去当前值
    targetSum = targetSum - root.left.val;
    if (traversal(root.left, targetSum)) {
        return true;
    }
    // 回溯要把当前的值加上
    targetSum = targetSum + root.left.val;
}
if (root.right != null) {
    // 先减去当前值
    targetSum = targetSum - root.right.val;
    if (traversal(root.right, targetSum)) {
        return true;
    }
    // 回溯要把当前的值加上
    targetSum = targetSum + root.right.val;
}
return false;

题目也要回溯过程,因为每次往下递归就会让target减去当前节点值,最后路径不符合条件return后还要把当前节点的值加到target上

完整代码:

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 = targetSum - root.left.val;
        if (traversal(root.left, targetSum)) {
            return true;
        }
        // 回溯要把当前的值加上
        targetSum = targetSum + root.left.val;
    }
    if (root.right != null) {
        // 先减去当前值
        targetSum = targetSum - root.right.val;
        if (traversal(root.right, targetSum)) {
            return true;
        }
        // 回溯要把当前的值加上
        targetSum = targetSum + root.right.val;
    }
    return false;
}

113. 路径总和II

力扣题目链接

给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。

叶子节点 是指没有子节点的节点。

示例 1:

输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:[[5,4,11,2],[5,8,4,5]]

示例 2:

输入:root = [1,2,3], targetSum = 5
输出:[]

示例 3:

输入:root = [1,2], targetSum = 0
输出:[]

递归思路

与112. 路径总和同理

因为只用找路径,所以递归函数不要返回值

注意:用一个数组保存结果,一个数组保存路径,一棵树可能有不止一条符合条件的路径,每次找到不符合要求的路径要进行回溯,既要回溯target减去的节点的值,也要回溯加入path的路径

难点

  • 在将path加入res的时候,add()里放的是一个地址值,不重新创建new ArrayList<>(path)的话,放多少个path都相当于是一个的地址

完整代码:

public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    if (root == null) {
        return res;
    }
    // 根节点不为null就加入
    path.add(root.val);
    traversal(root, targetSum - root.val, res, path);
    return res;

}

public void traversal(TreeNode root, int targetSum, List<List<Integer>> res, List<Integer> path) {

    // 遇到叶子节点
    if (root.left == null && root.right == null && targetSum == 0) {
        res.add(new ArrayList<>(path)); //此时加入的必须要new
        path.clear();
        return;
    }
    if (root.left == null && root.right == null && targetSum != 0) {
        return;
    }

    //没有遇到叶子节点往下遍历
    if (root.left != null) {
        // 节点不为null就加入path
        path.add(root.left.val);
        targetSum -= root.left.val;
        traversal(root.left, targetSum, res, path);
        // 回溯加上值且删掉加入path的元素
        targetSum += root.left.val;
        path.remove(path.size()-1);
    }
    if (root.right != null) {
        // 节点不为null就加入path
        path.add(root.right.val);
        targetSum -= root.right.val;
        traversal(root.right, targetSum, res, path);
        // 回溯加上值且删掉加入path的元素
        targetSum += root.right.val;
        path.remove(path.size()-1);
    }
    return;
}

106. 从中序与后序遍历序列构造二叉树

力扣题目链接

给定两个整数数组 inorderpostorder ,其中 inorder 是二叉树的中序遍历,postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树

示例1:

输入:inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]
输出:[3,9,20,null,null,15,7]

示例2:

输入:inorder = [-1], postorder = [-1]
输出:[-1]

思路

以后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来在切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。

一层一层切割,用递归一共分6步:

  • 第一步:如果数组大小为零的话,说明是空节点了。
  • 第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
  • 第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点
  • 第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)
  • 第五步:切割后序数组,切成后序左数组和后序右数组
  • 第六步:递归处理左区间和右区间

难点

  • 如何切中序数组?

用后序数组的最后一个元素在中序数组的位置index为界限来切分

  • 如何切后序数组?

用切分过的中序数组的左数组的大小来切后序数组,因为切分的中序的左区间一定和后续的左区间相同(就是中序数组大小一定是和后序数组的大小相同的)

力扣刷题day16|513找树左下角的值、112路径总和、113路径总和II、106从中序与后序遍历序列构造二叉树、105.从前序与中序遍历序列构造二叉树_第2张图片
完整代码:

public TreeNode buildTree(int[] inorder, int[] postorder) {
    // 如果数组大小为0,那么肯定组成不了树
    if (inorder.length == 0 || postorder.length == 0) return null;
    return traversal(inorder, postorder, 0, inorder.length, 0, postorder.length);
}

// 中序区间:[inorderBegin, inorderEnd),后序区间[postorderBegin, postorderEnd)
public TreeNode traversal(int[] inorder, int[] postorder, int inorderBegin, int inorderEnd, int postorderBegin, int postorderEnd) {
    // 第一步:如果数组大小为零的话,说明是空节点了。
    if (postorderBegin == postorderEnd) { //左开右闭,相等则len为0
        return null;
    }

    // 第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
    int rootValue = postorder[postorderEnd - 1];
    // 建树
    TreeNode root = new TreeNode(rootValue);

    // 当后序数组只有一个元素那就是叶子节点
    if (postorderEnd - postorderBegin == 1) {
        return root;
    }

    // 第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点
    int cutIndex;
    for (cutIndex = inorderBegin; cutIndex < inorderEnd; cutIndex++) {
        // 找到与root相同元素
        if (inorder[cutIndex] == rootValue) break;
    }

    // 第四步:切割中序数组,切成中序左数组和中序右数组
    // 左中序区间,左闭右开[leftInorderBegin, leftInorderEnd)
    int leftInorderBegin = inorderBegin;
    int leftInorderEnd = cutIndex;
    // 右中序区间,左闭右开[rightInorderBegin, rightInorderEnd)
    int rightInorderBegin = cutIndex + 1;
    int rightInorderEnd = inorderEnd;

    // 第五步:切割后序数组,切成后序左数组和后序右数组
    // 左后序区间,左闭右开[leftPostorderBegin, leftPostorderEnd)
    int leftPostorderBegin =  postorderBegin;
    int leftPostorderEnd = postorderBegin + (cutIndex - inorderBegin); // 终止位置是 需要加上 中序区间的大小size
    // 右后序区间,左闭右开[rightPostorderBegin, rightPostorderEnd)
    int rightPostorderBegin = postorderBegin + (cutIndex - inorderBegin);
    int rightPostorderEnd = postorderEnd - 1; // 排除最后一个元素,已经作为节点了

    // 第六步:递归处理左区间和右区间
    root.left = traversal(inorder, postorder, leftInorderBegin, leftInorderEnd, leftPostorderBegin, leftPostorderEnd);
    root.right = traversal(inorder,  postorder, rightInorderBegin, rightInorderEnd, rightPostorderBegin, rightPostorderEnd);

    return root;
}

105. 从前序与中序遍历序列构造二叉树

力扣题目地址

给定两个整数数组 preorderinorder ,其中 preorder 是二叉树的先序遍历inorder 是同一棵树的中序遍历,请构造二叉树并返回其根节点。

示例 1:

输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]

示例 2:

输入: preorder = [-1], inorder = [-1]
输出: [-1]

思路

和106.从中序与后序遍历序列构造二叉树一样的思路

此时前序数组的第一个元素就是根节点,再将根节点进去中序数组切分,然后中序数组的左数组切分前序数组除了第一位以后的数组

力扣刷题day16|513找树左下角的值、112路径总和、113路径总和II、106从中序与后序遍历序列构造二叉树、105.从前序与中序遍历序列构造二叉树_第3张图片

完整代码:

public TreeNode buildTree(int[] preorder, int[] inorder) {
    // 如果数组大小为0,那么肯定组成不了树
    if (inorder.length == 0 || preorder.length == 0) return null;
    return traversal(inorder, preorder, 0, inorder.length, 0, preorder.length);
}

// 中序区间:[inorderBegin, inorderEnd),后序区间[preorderBegin, preorderEnd)
public TreeNode traversal(int[] inorder, int[] preorder, int inorderBegin, int inorderEnd, int preorderBegin, int preorderEnd) {
    // 第一步:如果数组大小为零的话,说明是空节点了。
    if (preorderBegin == preorderEnd) { //左开右闭,相等则len为0
        return null;
    }

    // 第二步:如果不为空,那么取前序数组第一个元素作为节点元素。
    int rootValue = preorder[preorderBegin];
    // 建树
    TreeNode root = new TreeNode(rootValue);

    // 当前序数组只有一个元素那就是叶子节点
    if (preorderEnd - preorderBegin == 1) {
        return root;
    }

    // 第三步:找到前序数组最后一个元素在中序数组的位置,作为切割点
    int cutIndex;
    for (cutIndex = inorderBegin; cutIndex < inorderEnd; cutIndex++) {
        // 找到与root相同元素
        if (inorder[cutIndex] == rootValue) break;
    }

    // 第四步:切割中序数组,切成中序左数组和中序右数组
    // 左中序区间,左闭右开[leftInorderBegin, leftInorderEnd)
    int leftInorderBegin = inorderBegin;
    int leftInorderEnd = cutIndex;
    // 右中序区间,左闭右开[rightInorderBegin, rightInorderEnd)
    int rightInorderBegin = cutIndex + 1;
    int rightInorderEnd = inorderEnd;

    // 第五步:切割前序数组,切成前序左数组和前序右数组
    // 左前序区间,左闭右开[leftPreorderBegin, leftPreorderEnd)
    int leftPreorderBegin =  preorderBegin + 1;
    int leftPreorderEnd = preorderBegin + 1 + (cutIndex - inorderBegin); // 终止位置是 需要加上 中序区间的大小size
    // 右前序区间,左闭右开[rightPreorderBegin, rightPreorderEnd)
    int rightPreorderBegin = preorderBegin + 1 + (cutIndex - inorderBegin);
    int rightPreorderEnd = preorderEnd; // 排除最后一个元素,已经作为节点了

    // 第六步:递归处理左区间和右区间
    root.left = traversal(inorder, preorder, leftInorderBegin, leftInorderEnd, leftPreorderBegin, leftPreorderEnd);
    root.right = traversal(inorder,  preorder, rightInorderBegin, rightInorderEnd, rightPreorderBegin, rightPreorderEnd);

    return root;
}

你可能感兴趣的:(leetcode,leetcode,算法,java)