学习目标:
60天训练营打卡计划!
层序遍历法–迭代法
前后中序遍历–递归法
递归三部曲:
- 确定递归函数的参数和返回值
- 确定终止条件
- 确定单层递归的逻辑
学习内容:
513.找树左下角的值
- 方法一:层序遍历法(分清深度和最后的左下角的值!)
- 方法二:迭代遍历法
-
- 最好重新定义所谓的类,在其中增加最大深度和最大深度的左叶子节点数值
通过每次传入当前的层深度的递归和回溯来求解当前树的最大深度。
- 最好重新定义所谓的类,在其中增加最大深度和最大深度的左叶子节点数值
// 层序遍历
class Solution {
public int findBottomLeftValue(TreeNode root) {
Queue<TreeNode> que = new LinkedList<>();
int size = -1;
que.add(root);
// 该val就是最后的返回结果
int val = root.val;
while(!que.isEmpty()){
size = que.size();
// 确定每层的第一个值
int flag = 1;
while(size-- > 0){
TreeNode node = que.remove();
if(node.left != null) que.add(node.left);
if(node.right != null) que.add(node.right);
// 我们想要的结果一定在这个范围中。
if(node.left == null && node.right == null && flag == 1){
val = node.val;
flag--;
}
}
}
return val;
}
}
class Solution {
// 最大深度
private int maxDepth = -1;
// 最大深度最左侧的叶子结点的值
private int value = 0;
public int findBottomLeftValue(TreeNode root) {
traversal(root,0);
return value;
}
// 此处传的depth是为了确定当前的node节点所在的深度
private void traversal(TreeNode node, int depth){
if(node == null) return ;
if(node.left == null && node.right == null){
if(depth > maxDepth){
maxDepth = depth;
value = node.val;
}
}
// 隐式地体现递归和回溯
if(node.left != null ) traversal(node.left, depth + 1);
if(node.right != null) traversal(node.right, depth + 1);
return;
}
}
112. 路径总和
- 适合递归法做,和257. 二叉树的所有路径整体的逻辑很相似,要体现出递归和回溯的过程。
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
// 递归中没有对根节点的处理,所以就加到主程序里。
if(root == null) return false;
targetSum -= root.val;
return traversal(root,targetSum);
}
// 返回值为boolean是因为如果某条路已经满足题意就可以结束寻找了!
// count是用于计数的,判断这条路是否符合题意。
// 第一步:确定递归的返回值和参数
boolean traversal(TreeNode node, int count) {
// 已经遍历到叶子节点了,结束!
if(node.left == null && node.right == null && count == 0) return true;
if(node.left == null && node.right == null && count != 0) return false;
if(node.left != null){
count -= node.left.val;
if(traversal(node.left, count)) return true;
count += node.left.val;
}
if(node.right != null){
count -= node.right.val;
if(traversal(node.right, count)) return true;
count += node.right.val;
}
// 执行到这里一定是没有找到
return false;
}
}
113. 路径总和 Ⅱ
- 和112. 路径总和整体的逻辑很相似,要体现出递归和回溯的过程。
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> tmp = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
if(root == null) return res;
targetSum -= root.val;
tmp.add(root.val);
traversal(root, targetSum);
return res;
}
// 先找递归的返回值和参数
// 参数一定是树节点和总的数目
// 返回值可以不设,将其定义为类的一个参数
void traversal(TreeNode node ,int count){
// 结束条件
if(node.left == null && node.right == null){
if(count == 0)
// 这里为什么要新建?**因为add()添加的是引用。**
// 如果直接将tmp添加到res中,后续对tmp的修改也会影响已经添加到res中的路径。
// 通过创建一个新的ArrayList实例,可以保留路径的快照,防止修改影响已经存储的路径。
res.add(new ArrayList<>(tmp));
return;
}
// 单层递归逻辑
if(node.left != null){
tmp.add(node.left.val);
count -= node.left.val;
traversal(node.left, count);
tmp.remove(tmp.size() - 1);
count += node.left.val;
}
if(node.right != null){
tmp.add(node.right.val);
count -= node.right.val;
traversal(node.right, count);
tmp.remove(tmp.size() - 1);
count += node.right.val;
}
}
}
106.从中序与后序遍历序列构造二叉树
-
思路比较清晰。
-
第一次把根节点赋值赋错了,TreeNode root = new TreeNode(postorder[postorder.length - 1]);
-
按照0的位置赋值的。
-
此处使用了Arrays.copyOfRange(inorder, 0, index);来切割字符串。
-
- 好像蠢了,其实不需要物理切割,只需要指定新的数组的头和尾就行。
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]);
if(postorder.length == 1)
return root;
int index = 0;
for(index = 0; index < inorder.length; index++){
if(inorder[index] == root.val)
break;
}
if(index == inorder.length)
return root;
int[] leftin;
int[] rightin = null;;
int[] leftpost;
int[] rightpost = null;
// 切割数组,左闭右开
leftin = Arrays.copyOfRange(inorder, 0, index);
if(index + 1 < inorder.length)
rightin = Arrays.copyOfRange(inorder, index + 1, inorder.length);
leftpost = Arrays.copyOfRange(postorder, 0, index);
if(index < postorder.length - 1)
rightpost = Arrays.copyOfRange(postorder, index, postorder.length - 1);
root.left = buildTree(leftin, leftpost);
root.right = buildTree(rightin, rightpost);
return root;
}
}
// 只用一个数组的操作:
class Solution {
// 左闭右开
private TreeNode buildTreeLimit(int[] inorder, int[] postorder,
int instart, int inend, int poststart, int postend){
if(postend == 0 || inend == 0 || inend - instart == 0 || postend - poststart == 0){
return null;
}
TreeNode root = new TreeNode(postorder[postend - 1]);
if(postend - poststart == 1) return root;
int index = instart;
for(index = instart; index < inend; index++){
if(inorder[index] == root.val)
break;
}
// 有个误区:对于后序和中序遍历来说:
// 只能说后序遍历的左子树长度等于中序遍历的左子树长度(间隔)
// 同理,后序遍历的右子树长度等于中序遍历的右子树长度(间隔)
// 左子树
root.left = buildTreeLimit(inorder, postorder, instart, index,
poststart, poststart + index - instart);
// 右子树
root.right = buildTreeLimit(inorder, postorder, index + 1, inend,
poststart + index - instart , postend - 1);
return root;
}
public TreeNode buildTree(int[] inorder, int[] postorder) {
if(postorder == null || postorder.length == 0)
return null;
return buildTreeLimit(inorder, postorder, 0, inorder.length, 0, postorder.length);
}
}
105. 从前序与中序遍历序列构造二叉树
- 与106思路一致!
class Solution {
// 左闭右开
private TreeNode buildTreeLimit(int[] preorder, int[] inorder,
int prestart, int preend, int instart, int inend) {
if(preend == 0|| inend == 0 || preend - prestart == 0 || inend - instart == 0)
return null;
TreeNode root = new TreeNode(preorder[prestart]);
if(inend - instart == 1) return root;
int index = instart;
for(index = instart; index < inend; index++){
if(inorder[index] == root.val)
break;
}
// 构造左子树
root.left = buildTreeLimit(preorder, inorder,
prestart + 1, prestart + 1 +index - instart, instart, index);
// 构造右子树
root.right = buildTreeLimit(preorder, inorder,
prestart + 1 +index - instart, preend, index + 1, inend);
return root;
}
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(preorder.length == 0) return null;
return buildTreeLimit(preorder, inorder, 0, preorder.length, 0, inorder.length);
}
}
学习时间:
- 上午两小时,下午一个半小时,整理文档半小时。