代码随想录训练营day18| 513.找树左下角的值 112. 路径总和 106.从中序与后序遍历序列构造二叉树...

简介: 代码随想录训练营day18| 513.找树左下角的值 112. 路径总和 106.从中序与后序遍历序列构造二叉树...

前言

代码随想录算法训练营day18


一、Leetcode 513.找树左下角的值

1.题目

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

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

示例 1:

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

示例 2:

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

提示:

1. 二叉树的节点个数的范围是 [1,104]
2. -231 <= Node.val <= 231 - 1

来源:力扣(LeetCode) 链接:https://leetcode.cn/problems/find-bottom-left-tree-value

2.解题思路

方法一:深度优先搜索

使用 heightheight 记录遍历到的节点的高度,curValcurVal 记录高度在 curHeightcurHeight 的最左节点的值。在深度优先搜索时,我们先搜索当前节点的左子节点,再搜索当前节点的右子节点,然后判断当前节点的高度 heightheight 是否大于 curHeightcurHeight,如果是,那么将 curValcurVal 设置为当前结点的值,curHeightcurHeight 设置为 heightheight。

因为我们先遍历左子树,然后再遍历右子树,所以对同一高度的所有节点,最左节点肯定是最先被遍历到的。

3.代码实现

```java class Solution { int curVal = 0; int curHeight = 0;

1. public int findBottomLeftValue(TreeNode root) {
2.     int curHeight = 0;
3.     dfs(root, 0);
4. return curVal;
5. }
6. 
7. public void dfs(TreeNode root, int height) {
8. if (root == null) {
9. return;
10.     }
11.     height++;
12.     dfs(root.left, height);
13.     dfs(root.right, height);
14. if (height > curHeight) {
15.         curHeight = height;
16.         curVal = root.val;
17.     }
18. }

}

```

二、Leetcode 112. 路径总和

1.题目

给你二叉树的根节点 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. 树中节点的数目在范围 [0, 5000] 内
2. -1000 <= Node.val <= 1000
3. -1000 <= targetSum <= 1000

来源:力扣(LeetCode) 链接:https://leetcode.cn/problems/path-sum

2.解题思路

方法一:广度优先搜索

思路及算法

首先我们可以想到使用广度优先搜索的方式,记录从根节点到当前节点的路径和,以防止重复计算。

这样我们使用两个队列,分别存储将要遍历的节点,以及根节点到这些节点的路径和即可。

3.代码实现

```java class Solution { public boolean hasPathSum(TreeNode root, int sum) { if (root == null) { return false; } Queue queNode = new LinkedList (); Queue queVal = new LinkedList (); queNode.offer(root); queVal.offer(root.val); while (!queNode.isEmpty()) { TreeNode now = queNode.poll(); int temp = queVal.poll(); if (now.left == null && now.right == null) { if (temp == sum) { return true; } continue; } if (now.left != null) { queNode.offer(now.left); queVal.offer(now.left.val + temp); } if (now.right != null) { queNode.offer(now.right); queVal.offer(now.right.val + temp); } } return false; } }
```

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

1.题目

给定两个整数数组 inorder 和 postorder ,其中 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]

提示:

1. 1 <= inorder.length <= 3000
2. postorder.length == inorder.length
3. -3000 <= inorder[i], postorder[i] <= 3000
4. inorder 和 postorder 都由 不同 的值组成
5. postorder 中每一个值都在 inorder 中
6. inorder 保证是树的中序遍历
7. postorder 保证是树的后序遍历

来源:力扣(LeetCode) 链接:https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal

2.解题思路

方法二:迭代

思路

迭代法是一种非常巧妙的实现方法。迭代法的实现基于以下两点发现。

1. 如果将中序遍历反序,则得到反向的中序遍历,即每次遍历右孩子,再遍历根节点,最后遍历左孩子。
2. 如果将后序遍历反序,则得到反向的前序遍历,即每次遍历根节点,再遍历右孩子,最后遍历左孩子。

「反向」的意思是交换遍历左孩子和右孩子的顺序,即反向的遍历中,右孩子在左孩子之前被遍历。

因此可以使用和「105. 从前序与中序遍历序列构造二叉树」的迭代方法类似的方法构造二叉树。

对于后序遍历中的任意两个连续节点 uu 和 vv(在后序遍历中,uu 在 vv 的前面),根据后序遍历的流程,我们可以知道 uu 和 vv 只有两种可能的关系:

1. uu 是 vv 的右儿子。这是因为在遍历到 uu 之后,下一个遍历的节点就是 uu 的双亲节点,即 vv;
2. 
3. vv 没有右儿子,并且 uu 是 vv 的某个祖先节点(或者 vv 本身)的左儿子。如果 vv 没有右儿子,那么上一个遍历的节点就是 vv 的左儿子。如果 vv 没有左儿子,则从 vv 开始向上遍历 vv 的祖先节点,直到遇到一个有左儿子(且 vv 不在它的左儿子的子树中)的节点 vava,那么 uu 就是 vava 的左儿子。

第二种关系看上去有些复杂。我们举一个例子来说明其正确性,并在例子中给出我们的迭代算法。

例子

我们以树

1. 3
2. / \
3. 9  20
4. / \   \
5. 15 10   7
6. / \
7. 5   8
8.            \
9. 4

为例,它的中序遍历和后序遍历分别为

inorder = [15, 9, 10, 3, 20, 5, 7, 8, 4] postorder = [15, 10, 9, 5, 4, 8, 7, 20, 3]

我们用一个栈 stack 来维护「当前节点的所有还没有考虑过左儿子的祖先节点」,栈顶就是当前节点。也就是说,只有在栈中的节点才可能连接一个新的左儿子。同时,我们用一个指针 index 指向中序遍历的某个位置,初始值为 n - 1,其中 n 为数组的长度。index 对应的节点是「当前节点不断往右走达到的最终节点」,这也是符合反向中序遍历的,它的作用在下面的过程中会有所体现。

首先我们将根节点 3 入栈,再初始化 index 所指向的节点为 4,随后对于后序遍历中的每个节点,我们依次判断它是栈顶节点的右儿子,还是栈中某个节点的左儿子。

1. 我们遍历 20。20 一定是栈顶节点 3 的右儿子。我们使用反证法,假设 20 是 3 的左儿子,因为 20 和 3 中间不存在其他的节点,那么 3 没有右儿子,index 应该恰好指向 3,但实际上为 4,因此产生了矛盾。所以我们将 20 作为 3 的右儿子,并将 20 入栈。
2.     stack = [3, 20]
3. index -> inorder[8] = 4
4. 
5. 我们遍历 7,8 和 4。同理可得它们都是上一个节点(栈顶节点)的右儿子,所以它们会依次入栈。
6.     stack = [3, 20, 7, 8, 4]
7. index -> inorder[8] = 4
8. 
9. 我们遍历 5,这时情况就不一样了。我们发现 index 恰好指向当前的栈顶节点 4,也就是说 4 没有右儿子,那么 5 必须为栈中某个节点的左儿子。那么如何找到这个节点呢?栈中的节点的顺序和它们在反向前序遍历中出现的顺序是一致的,而且每一个节点的左儿子都还没有被遍历过,那么这些节点的顺序和它们在反向中序遍历中出现的顺序一定是相反的。
10. 
11.     这是因为栈中的任意两个相邻的节点,前者都是后者的某个祖先。并且我们知道,栈中的任意一个节点的左儿子还没有被遍历过,说明后者一定是前者右儿子的子树中的节点,那么后者就先于前者出现在反向中序遍历中。
12. 
13. 因此我们可以把 index 不断向左移动,并与栈顶节点进行比较。如果 index 对应的元素恰好等于栈顶节点,那么说明我们在反向中序遍历中找到了栈顶节点,所以将 index 减少 1 并弹出栈顶节点,直到 index 对应的元素不等于栈顶节点。按照这样的过程,我们弹出的最后一个节点 x 就是 5 的双亲节点,这是因为 5 出现在了 x 与 x 在栈中的下一个节点的反向中序遍历之间,因此 5 就是 x 的左儿子。
14. 
15. 回到我们的例子,我们会依次从栈顶弹出 4,8 和 7,并且将 index 向左移动了三次。我们将 5 作为最后弹出的节点 7 的左儿子,并将 5 入栈。
16.     stack = [3, 20, 5]
17. index -> inorder[5] = 5
18. 
19. 我们遍历 9。同理,index 恰好指向当前栈顶节点 5,那么我们会依次从栈顶弹出 5,20 和 3,并且将 index 向左移动了三次。我们将 9 作为最后弹出的节点 3 的左儿子,并将 9 入栈。
20.     stack = [9]
21. index -> inorder[2] = 10
22. 
23. 我们遍历 10,将 10 作为栈顶节点 9 的右儿子,并将 10 入栈。
24.     stack = [9, 10]
25. index -> inorder[2] = 10
26. 
27. 我们遍历 15。index 恰好指向当前栈顶节点 10,那么我们会依次从栈顶弹出 10 和 9,并且将 index 向左移动了两次。我们将 15 作为最后弹出的节点 9 的左儿子,并将 15 入栈。
28.     stack = [15]
29. index -> inorder[0] = 15

此时遍历结束,我们就构造出了正确的二叉树。

算法

我们归纳出上述例子中的算法流程:

1. 我们用一个栈和一个指针辅助进行二叉树的构造。初始时栈中存放了根节点(后序遍历的最后一个节点),指针指向中序遍历的最后一个节点;
2. 
3. 我们依次枚举后序遍历中除了第一个节点以外的每个节点。如果 index 恰好指向栈顶节点,那么我们不断地弹出栈顶节点并向左移动 index,并将当前节点作为最后一个弹出的节点的左儿子;如果 index 和栈顶节点不同,我们将当前节点作为栈顶节点的右儿子;
4. 
5. 无论是哪一种情况,我们最后都将当前的节点入栈。

3.代码实现

```java class Solution { public TreeNode buildTree(int[] inorder, int[] postorder) { if (postorder == null || postorder.length == 0) { return null; } TreeNode root = new TreeNode(postorder[postorder.length - 1]); Deque stack = new LinkedList (); stack.push(root); int inorderIndex = inorder.length - 1; for (int i = postorder.length - 2; i >= 0; i--) { int postorderVal = postorder[i]; TreeNode node = stack.peek(); if (node.val != inorder[inorderIndex]) { node.right = new TreeNode(postorderVal); stack.push(node.right); } else { while (!stack.isEmpty() && stack.peek().val == inorder[inorderIndex]) { node = stack.pop(); inorderIndex--; } node.left = new TreeNode(postorderVal); stack.push(node.left); } } return root; } }
```


相关文章
|
7月前
|
算法 vr&ar 图形学
☆打卡算法☆LeetCode 222. 完全二叉树的节点个数 算法解析
☆打卡算法☆LeetCode 222. 完全二叉树的节点个数 算法解析
代码随想录Day15 二叉树 LeetCodeT513 找树左下角的值 T112路径总和 T106 从中序和后序遍历构造二叉树
代码随想录Day15 二叉树 LeetCodeT513 找树左下角的值 T112路径总和 T106 从中序和后序遍历构造二叉树
44 0
|
7月前
|
Python Java Go
Python每日一练(20230401) 最大子序和、前序中序构造二叉树、岛屿数量
Python每日一练(20230401) 最大子序和、前序中序构造二叉树、岛屿数量
39 0
Python每日一练(20230401) 最大子序和、前序中序构造二叉树、岛屿数量
|
算法
代码随想录算法训练营第十八天 | 力扣 513. 找树左下角的值、112. 路径总和、113. 路径总和 II、106. 从中序与后序遍历序列构造二叉树、105. 从前序与中序遍历序列构造二叉树
代码随想录算法训练营第十八天 | 力扣 513. 找树左下角的值、112. 路径总和、113. 路径总和 II、106. 从中序与后序遍历序列构造二叉树、105. 从前序与中序遍历序列构造二叉树
57 0
|
算法
代码随想录算法训练营第十七天 | LeetCode 110. 平衡二叉树、257. 二叉树的所有路径、404. 左叶子之和
代码随想录算法训练营第十七天 | LeetCode 110. 平衡二叉树、257. 二叉树的所有路径、404. 左叶子之和
50 0
|
算法
代码随想录算法训练营第二十二天 | LeetCode 669. 修剪二叉搜索树、108. 将有序数组转换为二叉搜索树、538. 把二叉搜索树转换为累加树
代码随想录算法训练营第二十二天 | LeetCode 669. 修剪二叉搜索树、108. 将有序数组转换为二叉搜索树、538. 把二叉搜索树转换为累加树
54 0
|
算法
代码随想录算法训练营第十九天 | LeetCode 654. 最大二叉树、617. 合并二叉树、700. 二叉搜索树中的搜索、98. 验证二叉搜索树
代码随想录算法训练营第十九天 | LeetCode 654. 最大二叉树、617. 合并二叉树、700. 二叉搜索树中的搜索、98. 验证二叉搜索树
62 0
|
存储
LeetCode题:88合并两个有序数组,283移动零,448找到所有数组中消失的数字
LeetCode题:88合并两个有序数组,283移动零,448找到所有数组中消失的数字
72 0