代码随想录算法训练营 DAY 18 | 513.找树左下角的值 513.找树左下角的值 106.从中序与后序遍历序列构造二叉树

513.找树左下角的值

找二叉树最底层 最左边节点的值。迭代法层序遍历比较容易

注意:左下角的节点,不一定是左孩子!!只是最底层最左边的节点就行(可能是右孩子)

递归法

  • 怎么找最底层?
    • 只要找深度最大的叶子节点

在递归的同时记录下最大深度maxDepth,要初始化成-1,方便比较。用result记录左下角节点的值。maxDepth和result都要在类里初始化!不能放在方法里!因为这两个是全局的 depth初始化成0!!!

  1. 确定递归函数

本题我们需要不断更新depth(初始化成0),所以放到参数里 不需要返回值了

void traversal(root, depth) {}
  1. 终止条件

遍历到叶子节点(左右孩子都为空),然后比较当前depth,如果大于maxDepth就更新,统计记录当前节点的数值。这样一定记录的是depth相同的节点里最左边的!(同层的不会更新result)

if(root.left == null && root.right == null) {
	if(depth > maxDepth) {
		maxDepth = depth;
		result = root.val;
	}
}
  1. 单层递归逻辑

如果左/右孩子不为空,就往左 往右递归,需要回溯,恢复现场 depth–

  • 为什么要回溯?因为我们需要回到上一个根节点,递归回来后 depth就要回退
if(root.left != null) {
	depth++;
	traversal(root.left,depth);
	depth--;  
    //上面这三行可以直接精简成 traversal(root.left,depth+1);
    //因为depth的实际值并没有改变,就不用--了
}
if(root.right != null) {
	...
}
  • 这题为什么没有遍历 中 的逻辑?不需要啊!他路过中了,但是他判断出这是中后 就不处理中了。

  • java代码

class Solution {
    private int maxDepth = -1;
    private int result = 0;

    public void traversal(TreeNode root, int depth) {
        if(root.left == null && root.right == null) {  //遇到叶子节点了
            if(depth > maxDepth) {
                maxDepth = depth;
                result = root.val;
            }
        }
        if(root.left != null) {
            depth++;
            traversal(root.left, depth);
            depth--;  //回溯 恢复现场
        }
        if(root.right != null) {
            depth++;
            traversal(root.right, depth);
            depth--;
        }
    }

    public int findBottomLeftValue(TreeNode root) {
        int depth = 0;
        traversal(root,depth);
        return result;
    }
}

112.路径总和

是否需要显示回溯,需要区分自己传的是值还是引用!

  • 直接传targetSum进去,然后每遍历一个节点就count-=。如果到了叶子节点,同时count已经为0了,就return true;

  • ==如果有一条路径满足了,我们需要一路把true返回给根节点!==所以在递归左右孩子的时候,要判断传回来的是不是true,是就一路回传。(说明左/右方向上有正确答案!

  1. 确定递归函数的参数和返回类型

参数:需要二叉树的根节点,还需要一个计数器,这个计数器用来计算二叉树的一条边之和是否正好是目标和,计数器为int型。

再来看返回值,递归函数什么时候需要返回值?什么时候不需要返回值?这里总结如下三点:

  • 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。
  • 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。
  • 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(本题的情况)

而本题我们要找一条符合条件的路径,所以递归函数需要返回值,及时返回,那么返回类型是什么呢?

在这里插入图片描述

bool traversal(treenode* cur, int count)   // 注意函数的返回类型
  1. 确定终止条件

首先计数器如何统计这一条路径的和呢?

不要去累加然后判断是否等于目标和,那么代码比较麻烦,可以用递减,让计数器count初始为目标和,然后每次减去遍历路径节点上的数值。

如果最后count == 0,同时到了叶子节点的话,说明找到了目标和。

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

递归终止条件代码如下:

if (!cur->left && !cur->right && count == 0) return true; // 遇到叶子节点,并且计数为0
if (!cur->left && !cur->right) return false; // 遇到叶子节点而没有找到合适的边,直接返回
  1. 确定单层递归的逻辑

因为终止条件是判断叶子节点,所以递归的过程中就不要让空节点进入递归了。

递归函数是有返回值的,如果递归函数返回true,说明找到了合适的路径,应该立刻返回。

代码如下:

if (cur->left) { // 左 (空节点不遍历)
    // 遇到叶子节点返回true,则直接返回true
    if (traversal(cur->left, count - cur->left->val)) return true; // 注意这里有回溯的逻辑
}
if (cur->right) { // 右 (空节点不遍历)
    // 遇到叶子节点返回true,则直接返回true
    if (traversal(cur->right, count - cur->right->val)) return true; // 注意这里有回溯的逻辑
}
return false;
class Solution {
    public boolean traversal(TreeNode node,int count) {
        if(node.left == null & node.right == null && count == 0) {  //遇到叶子节点,且count已经为0了
            return true;
        }
        if(node.left == null & node.right == null) return false; //遇到叶子节点了,但count不为0
        if(node.left != null) {
            //在递归之前先把count--
            count -= node.left.val;
            if(traversal(node.left, count)) return true;  //要在这里判断是否左孩子是叶子节点且为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;
    }

    public boolean hasPathSum(TreeNode root, int targetSum) {
        if(root == null) return false;
        targetSum -= root.val;  //传进去之前先把root的val值减掉
        return traversal(root, targetSum);
    }
}

113.路径总和 II

注意递归函数变成void了,参数要传node,count, result, path

  • 完整代码
class Solution {
    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        List<List<Integer>> res = new ArrayList<>();
        if (root == null) return res; // 非空判断

        List<Integer> path = new LinkedList<>();
        preorderdfs(root, targetSum, res, path);
        return res;
    }

    public void preorderdfs(TreeNode root, int targetsum, List<List<Integer>> res, List<Integer> path) {
        path.add(root.val);
        // 遇到了叶子节点
        if (root.left == null && root.right == null) {
            // 找到了和为 targetsum 的路径
            if (targetsum - root.val == 0) {
                res.add(new ArrayList<>(path));
            }
            return; // 如果和不为 targetsum,返回
        }

        if (root.left != null) {
            preorderdfs(root.left, targetsum - root.val, res, path);
            path.remove(path.size() - 1); // 回溯
        }
        if (root.right != null) {
            preorderdfs(root.right, targetsum - root.val, res, path);
            path.remove(path.size() - 1); // 回溯
        }
    }
}

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

中序:左中右

后序:左右中

这棵树的根节点一定是后序遍历序列的最后一个节点!找到根节点后,再用它 在中序序列里分割成左 和 右

  • 大体逻辑:

在这里插入图片描述

  • 切中序是用index遍历找 得到左中序,右中序
  • 拿中序找到的左区间的大小 去切后序里的左区间 得到左后序,右后序
  • 切割的循环不变量:左闭右开
  • 左右递归是传入得到的左中序,左后序 和 右中序,右后序,分别构造左子树和右子树
  1. 确定递归函数
Treenode traversal(int[] inorder, int[] postorder)
  1. 确定终止条件
    // 第一步
    if (postorder.size() == 0) return NULL;

    // 第二步:后序遍历数组最后一个元素,就是当前的中间节点
    int rootValue = postorder[postorder.size() - 1];
    TreeNode root = new TreeNode(rootValue);

    // 叶子节点
    if (postorder.size() == 1) return root;
  1. 单层递归逻辑
   // 第三步:找切割点
    int delimiterIndex;
    for (delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex++) {
        if (inorder[delimiterIndex] == rootValue) break;
    }

    // 第四步:切割中序数组,得到 中序左数组和中序右数组
    // 第五步:切割后序数组,得到 后序左数组和后序右数组

    // 第六步
    root.left = traversal(中序左数组, 后序左数组);
    root.right = traversal(中序右数组, 后序右数组);

    return root;
  • 完整代码
class Solution {
    Map<Integer, Integer> map;  // 方便根据数值查找位置
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        map = new HashMap<>();
        for (int i = 0; i < inorder.length; i++) { // 用map保存中序序列的数值对应位置
            map.put(inorder[i], i);
        }

        return findNode(inorder,  0, inorder.length, postorder,0, postorder.length);  // 前闭后开
    }

    public TreeNode findNode(int[] inorder, int inBegin, int inEnd, int[] postorder, int postBegin, int postEnd) {
        // 参数里的范围都是前闭后开
        if (inBegin >= inEnd || postBegin >= postEnd) {  // 不满足左闭右开,说明没有元素,返回空树
            return null;
        }
        int rootIndex = map.get(postorder[postEnd - 1]);  // 找到后序遍历的最后一个元素在中序遍历中的位置
        TreeNode root = new TreeNode(inorder[rootIndex]);  // 构造结点
        int lenOfLeft = rootIndex - inBegin;  // 保存中序左子树个数,用来确定后序数列的个数
        root.left = findNode(inorder, inBegin, rootIndex,
                            postorder, postBegin, postBegin + lenOfLeft);
        root.right = findNode(inorder, rootIndex + 1, inEnd,
                            postorder, postBegin + lenOfLeft, postEnd - 1);

        return root;
    }
}

day18总结

  1. 递归函数什么时候需要返回值?
  • 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。
  • 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。
  • 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(本题的情况)
  1. list:

add(E)、add(int, E)、remove(int)、remove(Object)、set(int, E)、forEach()、iterator()
add(E):在末尾增加元素
add(int, E):在指定位置增加元素

remove(int):删除指定位置的元素
remove(Object):删除指定元素

set(int, E):修改指定位置的元素

forEach():for循环遍历集合
iterator():迭代器遍历元素

LinkedList特有方法:

addFirst(E e):在链表头部插入一个元素;
addLast(E e):在链表尾部添加一个元素;
push(E e):与addFirst方法一致;
offer(E e):在链表尾部插入一个元素;

removeFirst(E e):删除头,获取元素并删除;
removeLast(E e):删除尾,获取元素并删除;
pollFirst():删除头;
pollLast():删除尾;

getFirst():获取第一个元素;
getLast():获取最后一个元素;
peek():获取第一个元素,但是不移除;
pollFirst():查询并删除头;
pollLast():删除尾;
poll():查询并移除第一个元素;

class Solution {
    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        List<List<Integer>> res = new ArrayList<>();
        if (root == null) return res; // 非空判断

        List<Integer> path = new LinkedList<>();
        preorderdfs(root, targetSum, res, path);
        return res;
    }

    public void preorderdfs(TreeNode root, int targetsum, List<List<Integer>> res, List<Integer> path) {
        path.add(root.val);
        // 遇到了叶子节点
        if (root.left == null && root.right == null) {
            // 找到了和为 targetsum 的路径
            if (targetsum - root.val == 0) {
                res.add(new ArrayList<>(path));
            }
            return; // 如果和不为 targetsum,返回
        }

        if (root.left != null) {
            preorderdfs(root.left, targetsum - root.val, res, path);
            path.remove(path.size() - 1); // 回溯
        }
        if (root.right != null) {
            preorderdfs(root.right, targetsum - root.val, res, path);
            path.remove(path.size() - 1); // 回溯
        }
    }
}

相关推荐

最近更新

  1. TCP协议是安全的吗?

    2024-03-28 15:32:04       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-28 15:32:04       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-28 15:32:04       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-28 15:32:04       20 阅读

热门阅读

  1. 突破编程_C++_查找算法(二叉树查找)

    2024-03-28 15:32:04       21 阅读
  2. Spring全家桶涉及的注解

    2024-03-28 15:32:04       22 阅读
  3. Element-UI中el-cascader级联选择器获取label值

    2024-03-28 15:32:04       20 阅读
  4. Bean对象拷贝工具封装

    2024-03-28 15:32:04       21 阅读
  5. 若依分离版 —引入echart连接Springboot后端

    2024-03-28 15:32:04       17 阅读
  6. openGauss的索引组织表

    2024-03-28 15:32:04       20 阅读
  7. pytorch常用的模块函数汇总(2)

    2024-03-28 15:32:04       20 阅读