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

二叉树的遍历与处理

例如,

  • swap:处理root节点
  • invertTree(root->left):在其内部,会处理root->left
  • invertTree(root->right):在其内部,会处理root->right

因此,无论是swap还是inverTree,都是在处理。因此,他们的顺序,就是遍历的顺序。

class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        if (root == NULL) return root;
        swap(root->left, root->right);  // 中
        invertTree(root->left);         // 左
        invertTree(root->right);        // 右
        return root;
    }
};

对于404左叶子之和,不是很直观:实际上是后序遍历。可以体会一下。
只有先计算出左孩子、右孩子的对应值,才会计算该节点的对应值。计算之后,再返回。

// 不直观
class Solution {
    public int sumOfLeftLeaves(TreeNode root) {
        return getLeftSum(root);
    }

    private int getLeftSum(TreeNode root){
        // 空节点
        if(root==null) return 0;

        // 左孩子是叶子节点
        if(root.left!=null && root.left.left==null && root.left.right==null) return root.left.val + getLeftSum(root.right);
        else return getLeftSum(root.left) + getLeftSum(root.right);
    }
}

// 代码随想录版本
class Solution {
    public int sumOfLeftLeaves(TreeNode root) {
        if (root == null) return 0;
        int leftValue = sumOfLeftLeaves(root.left);    // 左
        int rightValue = sumOfLeftLeaves(root.right);  // 右
                                                       
        int midValue = 0;
        if (root.left != null && root.left.left == null && root.left.right == null) { 
            midValue = root.left.val;
        }
        int sum = midValue + leftValue + rightValue;  // 中
        return sum;
    }
}

513. 找树左下角的值

class Solution {
    public int findBottomLeftValue(TreeNode root) {
        Deque<TreeNode> dq = new LinkedList<>();
        dq.offer(root);
        int count = dq.size();
        TreeNode res = new TreeNode();
        while(!dq.isEmpty()){
            res = dq.poll(); count--;
            if(res.left!=null) dq.offer(res.left);
            if(res.right!=null) dq.offer(res.right);
            while(count>0){
                TreeNode node = dq.poll(); count--;
                if(node.left!=null) dq.offer(node.left);
                if(node.right!=null) dq.offer(node.right);
            }
            count=dq.size();
        }
        return res.val;
    }
}

112. 路径总和Ⅰ

回溯:sum或者target,在作为回溯的参数时临时改变了。
姑且也算是回溯吧……

class Solution {
    public boolean hasPathSum(TreeNode root, int targetSum) {
        if(root==null) return false;
        return backtracking(root, 0, targetSum);
    }

    private boolean backtracking(TreeNode node, int sum, int targetSum){ // sum:不包括node
        sum += node.val;
        // 叶子节点
        if(node.left==null && node.right==null) return sum==targetSum? true: false;
        // 非叶子节点
        else if(node.left!=null && node.right==null) return backtracking(node.left, sum, targetSum);
        else if(node.right!=null && node.left==null) return backtracking(node.right, sum, targetSum);
        else return backtracking(node.left, sum, targetSum) || backtracking(node.right, sum, targetSum);
    }
}

代码随想录:

  1. 判空逻辑:空节点、叶子节点
  2. sumtargetSum:合并成为一个,表示与目标的差值。
  3. 回溯思想:target = targetSum-root.val
  4. 判断逻辑:因为会判空,所以不需要考虑迭代的子节点是否为空
// lc112 简洁方法
class Solution {
    public boolean hasPathSum(TreeNode root, int targetSum) {

        if (root == null) return false; // 为空退出

        // 叶子节点判断是否符合
        if (root.left == null && root.right == null) return root.val == targetSum;

        // 求两侧分支的路径和
        return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val);
    }
}

113. 路径总和Ⅱ(重点:回溯!回溯!)

class Solution {
    public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
        result = new ArrayList<>();
        ArrayList<Integer> path = new ArrayList<>();
        backtracking(root, targetSum, path);
        return result;
    }


    private List<List<Integer>> result;
    private void backtracking(TreeNode node, int targetSum, ArrayList<Integer> path){
        // 边缘条件
        if(node==null) return;
        path.add(node.val);
        if(node.left==null && node.right==null){
            if(targetSum == path.stream().reduce(0, Integer::sum)) result.add((List<Integer>)path.clone());
            return;
        }
        // 递归与回溯
        if(node.left!=null) {
            backtracking(node.left, targetSum, path);
            path.remove(path.size()-1);
        }
        if(node.right!=null){
            backtracking(node.right, targetSum, path);
            path.remove(path.size()-1);
        }
    }
}

思路:

  1. 目的是找到所有结果:没有返回值(因为需要遍历整棵树)
  2. 不涉及多线程,可以将pathresult放在成员变量,就不用放在形参了。
  3. 判空:空节点也可,叶子节点也可。
  4. 求和:用target = targetSum - node.val... ,一个值代替两个值。
  5. 回溯:路径需要回溯。但是target不需要……我还是不认为target是回溯。
  6. 使用LinkedList:可以很方便remove最后一个元素。
  7. 可以直接添加new LinkedList<>(path),并不需要clone()
  8. 递归与回溯不是一一对应?
    实际上还是对应着的,只不过统一安排在本层末尾回溯,而不是上一层回溯下一层的。回溯完后,最终返回上一层。
    如果恰好是那个叶节点,添加了result之后,却没有直接return,最后也会在本层末尾回溯。
    如果还有孩子,无论是空还是非空,此时下一层回溯不会添加元素,即使添加了,末尾也会回溯。自然不可再remove;
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); // 回溯
        }
    }
}
// 解法2
class Solution {
    List<List<Integer>> result;
    LinkedList<Integer> path;
    public List<List<Integer>> pathSum (TreeNode root,int targetSum) {
        result = new LinkedList<>();
        path = new LinkedList<>();
        travesal(root, targetSum);
        return result;
    }
    private void travesal(TreeNode root,  int count) {
        if (root == null) return;
        path.offer(root.val);
        count -= root.val;
        if (root.left == null && root.right == null && count == 0) {
            result.add(new LinkedList<>(path));
        }
        travesal(root.left, count);
        travesal(root.right, count);
        path.removeLast(); // 回溯
    }
}

106. 从中序与后序遍历序列构造二叉树(两个顺序构造一个唯一的二叉树)

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

每次递归,都定义了新数组,耗时耗空间。

class Solution {
    public TreeNode buildTree(int[] inorder, int[] postorder) {
        result = new TreeNode();
        build(inorder, postorder, result);
        return result;
    }

    private TreeNode result;
    private void build(int[] inorder, int[] postorder, TreeNode node){
        // 赋值
        int length = postorder.length, leftLength = 0, rightLength = 0;
        node.val = postorder[length - 1];
        // 分割:左闭右闭?
        for(int i=0; i<length; i++){
            if(inorder[i]==node.val){
                leftLength = i;
                break;
            }
        }
        rightLength = length - leftLength - 1;
        if(leftLength!=0) {
            TreeNode leftNode = new TreeNode();
            node.left = leftNode;
            build(Arrays.copyOfRange(inorder, 0, leftLength), 
                Arrays.copyOfRange(postorder, 0, leftLength),
                leftNode);
        }
        if(rightLength!=0){
            TreeNode rightNode = new TreeNode();
            node.right = rightNode;
            build(Arrays.copyOfRange(inorder, leftLength+1, length), 
                Arrays.copyOfRange(postorder, leftLength, length-1),
                rightNode);
        } 
    }
}

代码随想录

  1. 下标索引分割:分别由start与end
  2. map:快速获取位置。(树中没有重复节点)
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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值