所用代码 java
找树左下角的值 LeetCode 513
题目链接:找树左下角的值 LeetCode 513 - 中等
思路
层序遍历,每次遍历时把每一层的第一个值保留就行了
class Solution {
public int findBottomLeftValue(TreeNode root) {
int num = 0;
Deque<TreeNode> deque = new ArrayDeque<>();
if (root != null) deque.offer(root);
while (!deque.isEmpty()){
int size = deque.size();
for (int i = 0; i < size; i++) {
TreeNode node = deque.poll();
// 每一层的第一个元素保留(即为最左边的结点)
if (i == 0) num = node.val;
if (node.left != null) deque.offer(node.left);
if (node.right != null) deque.offer(node.right);
}
}
return num;
}
}
递归:首先深度要最深,其次为最左边的叶子结点
class Solution {
int maxDeep = Integer.MIN_VALUE;
int value = 0;
public int findBottomLeftValue(TreeNode root) {
traversal(root,0);
return value;
}
public void traversal(TreeNode node, int deep){
if (node == null) return;
// 遍历到叶子结点就结束
if (node.left == null && node.right == null) {
if (deep > maxDeep){
maxDeep = deep;
value = node.val;
}
}
// 左 traversal(node.left, deep + 1)
if (node.left != null){
deep++;
traversal(node.left, deep);
// 回溯的处理逻辑在递归函数的后面
deep--;
}
// 右 traversal(node.right, deep + 1)
if (node.right != null){
deep++;
traversal(node.right,deep);
deep--;
}
}
}
总结
本题层序比较简单,递归主要考虑到回溯,每次回溯时深度要减1
递归为何传递时traversal(node.left, deep + 1)有回溯?
可以看到deep每次+1时就进行了一个递归,栈就堆一层,当我们返回最上层的deep=3的结果时,就进行deep=2的操作,然后又继续往下去回溯,此时的deep值就是原来的结果
路径总和 LeetCode 112
题目链接:路径总和 LeetCode 112 - 简单
思路
把所有路径搜集到一个list里面,然后对比有没有target的值,这题感觉和二叉树的所有路径类似
我自己写的,比较冗余:
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
if (root == null) return false;
List<Integer> list = new ArrayList<>();
traversal(root, list, 0);
// System.out.println("size = " + list.size()); 所以才看了下list的长度
for (int i : list) {
// System.out.println("i=" + i); 之前误判只有两个值,其实以及返回了
if (i == targetSum) return true;
}
return false;
}
public void traversal(TreeNode node, List<Integer> list, int sum){
// 叶子结点返回
if (node.left == null && node.right == null) {
// System.out.println("到达结点:" + node.val);
// 到达叶子结点就加上叶子结点的值
sum += node.val;
list.add(sum);
return;
}
// 左
if (node.left != null){
sum += node.val;
traversal(node.left,list,sum);
// 回溯,需要减去该结点的值
sum -= node.val;
}
// 右
if (node.right != null){
sum += node.val;
traversal(node.right,list,sum);
sum -= node.val;
}
}
}
改进后的代码:
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
// 避免递归函数传入空结点
if (root == null) return false;
// 由于没有减根结点,所以要提前减去
return traversal(root, targetSum - root.val);
}
public boolean traversal(TreeNode node, int target){
System.out.println("target = " + target);
// 返回情况两种:到达叶子结点后,target是否被减为0了
// 我们不需要遍历所有结点,遇到正确的就可以一直向上返回true
if (node.left == null && node.right == null && target == 0){
return true;
}
if (node.left == null && node.right == null && target != 0){
return false;
}
// 左 - 因为前面没有判断左右孩子结点是否存在,所有要在这里判断
//if (traversal(node.left, target - node.left.val)) return true;
if (node.left != null){
// 存在左子树就减去 左子树 的值,后面再加回来
target -= node.left.val;
// 如果左孩子传回来的值是ture,就直接向上返回true
if (traversal(node.left, target)) return true;
// 否则就回溯,然后向右递归,把减掉的值加上
target += node.left.val;
}
// 右 - 同理 if (traversal(node.right, target - node.right.val)) return true;
if (node.right != null){
target -= node.right.val;
if (traversal(node.right, target)) return true;
target += node.right.val;
}
// 没有目标值刚好减完的情况,就返回false
return false;
}
}
更精简的代码:
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
if (root == null) return false;
// 直到最后判断targetSum剩下的值是否等于叶子结点的值
if (root.left == null && root.right == null) {
return targetSum == root.val;
}
// 一开始减去的是根结点的值
// 后面是递归和回溯过程的加减结点值
return hasPathSum(root.left, targetSum - root.val) ||
hasPathSum(root.right, targetSum - root.val);
}
}
总结
递归的话包含着回溯的过程,一开始学习的时候可以把整个过程写完,没必要写太精简的,多理解然后再浓缩。
路径总和 LeetCode 113
题目链接:路径总和 LeetCode 113 - 中等
思路
和上面一个题的方法应该是一样的,需多传一个path来接受路径值
class Solution {
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
if (root == null) return res;
List<Integer> path = new ArrayList<>();
path.add(root.val);
traversal(root, path, targetSum - root.val);
return res;
}
public void traversal(TreeNode node, List<Integer> path, int target){
if (node.left == null && node.right == null && target == 0){
// System.out.println("path = " + path);
// 需要新建一个list去存值,以免存的引用变量path会改变
// List<Integer> list = new ArrayList<>(path);
List<Integer> list = new ArrayList<>();
// 复制path里面的所有值到list
list.addAll(path);
// 若直接写res.add(path),上面res的值是[[5],[5]]
// 由于我们添加的path是path的引用(地址值),后面path再改变的话res里面的path也会改变
res.add(list);
// System.out.println("res = " + res);
return;
}
if (node.left == null && node.right == null && target != 0){
return;
}
//左
if (node.left != null){
target -= node.left.val;
path.add(node.left.val);
traversal(node.left, path, target);
target += node.left.val;
path.remove(path.size() - 1);
}
//右
if (node.right != null){
target -= node.right.val;
path.add(node.right.val);
traversal(node.right, path, target);
target += node.right.val;
path.remove(path.size() - 1);
}
}
}
总结
本题是在上次的基础上进行的拓展,多添加了一个变量,可以直接写在参数里面调用。要注意的是每次找到的path路径需重新new一个list来存储,可以直接List<Integer> list = new ArrayList<>(path);
这样进行list的copy,比addAll要方便。
从中序与后序遍历序列构造二叉树
题目链接:
从中序与后序遍历序列构造二叉树 LeetCode 106 - 中等
从前序与中序遍历序列构造二叉树 LeetCode 105 - 中等
思路
知道怎么构造二叉树,但是写不出来代码
中序 + 后序
- 后序数组为0,空结点
- 后序数组最后一个元素为结点元素
- 寻找中序数组位置作切割点
- 切中序数组
- 切后序数组
- 递归处理左区间右区间
根据卡哥思路一次过,太棒了!!这思路太清晰了。视频地址:中序 + 后序构建二叉树
class Solution {
public TreeNode buildTree(int[] inorder, int[] postorder) {
return traversal(inorder,postorder);
}
public TreeNode traversal(int[] inorder, int[] postorder){
if (postorder.length == 0) return null;
int rootValue = postorder[postorder.length - 1];
TreeNode root = new TreeNode(rootValue);
// 当遍历到叶子结点时,就把该叶子结点向上传递
// 或者只有一个结点时,也返回。
if (postorder.length == 1) return root;
// 3、寻找切割位置 - 利用中序
int index = 0;
for (index = 0; index < inorder.length; index++) {
if (inorder[index] == rootValue) break;
}
// 4、切中序数组,根据根结点的位置切割 -- copyOfRange 左闭右开
// Arrays.copyOfRange(要拷贝的数组,起始索引位置,终止索引位置)
int[] leftInorder = Arrays.copyOfRange(inorder, 0, index);
int[] rightInorder = Arrays.copyOfRange(inorder, index + 1, inorder.length);
// 5、切后序数组,根据中序的左中序长度切割
int[] leftPostorder = Arrays.copyOfRange(postorder, 0, leftInorder.length);
int[] rightPostorder =
Arrays.copyOfRange(postorder, leftPostorder.length, postorder.length-1);
// 6、递归处理左区间和右区间
root.left = traversal(leftInorder, leftPostorder);
root.right = traversal(rightInorder, rightPostorder);
// 返回二叉树的根结点
return root;
}
}
按照前面这个思路的话,可以写出 中序 + 前序的过程
- 前序数组为0,空结点
- 前序数组的第一个元素为结点元素
- 寻找中序数组的位置作为切割点
- 切割中序数组
- 切割前序数组
- 递归处理左区间和右区间
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
return traversal(preorder, inorder);
}
public TreeNode traversal(int[] preorder, int[] inorder){
// 1、前序数组为空,空结点
if (preorder.length == 0) return null;
// 2、前序数组第一个元素为结点元素
int rootValue = preorder[0];
TreeNode root = new TreeNode(rootValue);
// 只有一个元素或者遍历到了根结点就返回该结点
if (preorder.length == 1) return root;
// 3、寻找切割点 -- 通过中序数组
int index = 0; // index后面切割会用到,所以定义在外边
for (index = 0; index < inorder.length; index++) {
if (rootValue == inorder[index]) break;
}
// 4、切割中序数组
int[] leftInorder = Arrays.copyOfRange(inorder, 0, index);
int[] rightInorder = Arrays.copyOfRange(inorder, index + 1, inorder.length);
// 5、切割前序数组
int lenLeft = leftInorder.length;
int[] leftPreorder = Arrays.copyOfRange(preorder, 1, lenLeft + 1);
int lenRight = leftPreorder.length;
int[] rightPreorder =
Arrays.copyOfRange(preorder, 1 + lenRight, preorder.length);
// 6、左右区间分别递归
root.left = traversal(leftPreorder, leftInorder);
root.right = traversal(rightPreorder, rightInorder);
return root;
}
}
总结
本题主要是有点麻烦,需考虑的事情比较多,细节处理也多,还要有非常缜密的思维,该题可以说是非常的难了。
另外一种使用索引的方式来进行构造二叉树(前序为例):
使用这种方法我们就不需要创建新的数组,而直接利用数组的索引,使其找到正确的创建二叉树的根结点值,每次只需要传入数组左右区间的位置,然后以此递归
class Solution {
Map<Integer, Integer> map = new HashMap<>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
// 存储中序数组索引,以便后续寻找
for (int i = 0; i < inorder.length; i++) {
map.put(inorder[i], i);
}
return traversal(preorder, 0, preorder.length, inorder, 0, inorder.length);
}
public TreeNode traversal(int[] preorder, int preBegin, int preEnd, int[] inorder, int inBegin, int inEnd){
// 1、若前序数组为空(使用索引操作),则返回null
if (preEnd - preBegin <= 0) return null;
// 2、创建结点 ==> 若只有还剩一个元素则返回(只有一个结点、递归到叶子结点)
TreeNode root = new TreeNode(preorder[preBegin]);
// 由于前序和中序一样,所以只用判断前序就行了
if (preEnd - preBegin == 1) return root;
// 3、找到中序中结点的索引 -- 前序数组的第一个数
int indexValue = map.get(preorder[preBegin]);
// 4、找到切割中序的索引位置 -- 统一左闭右开
int leftInorderBegin = inBegin;
int leftInorderEnd = indexValue;
int rightInorderBegin = indexValue + 1;
int rightInorderEnd = inEnd;
// 5、切割前序索引位置
int leftPreorderBegin = preBegin + 1;
int leftPreorderEnd = leftPreorderBegin + (leftInorderEnd - leftInorderBegin);
int rightPreorderBegin = leftPreorderEnd;
int rightPreorderEnd = preEnd;
// 左右递归构建二叉树,这里接受前面返回的root(孩子结点)
root.left = traversal(preorder, leftPreorderBegin, leftPreorderEnd, inorder, leftInorderBegin, leftInorderEnd);
root.right = traversal(preorder, rightPreorderBegin, rightPreorderEnd, inorder, rightInorderBegin, rightInorderEnd);
return root;
}
}