Leetcode高频算法题:二叉树篇(一)

本文介绍了与二叉树相关的算法问题,包括计算最大深度、根据遍历序列构造二叉树、翻转树、合并树以及寻找二叉树的直径、判断是否为平衡二叉树、计算最小深度、查找子树等。这些问题涉及递归和树的遍历策略,是数据结构与算法的重要应用。
摘要由CSDN通过智能技术生成

目录

1. 二叉树的最大深度给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。说明: 叶子节点是指没有子节点的节点。

示例:
给定二叉树[3,9,20,null,null,15,7],返回它的最大深度3。
在这里插入图片描述

public int maxDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
    }

2. 从前序与中序遍历序列构造二叉树:给定一棵树的前序遍历 preorder 与中序遍历 inorder。请构造二叉树并返回其根节点。

示例:
在这里插入图片描述

输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]

Map<Integer, Integer> inMap = new HashMap();
public TreeNode buildTree(int[] preorder, int[] inorder) {
    int preLength = preorder.length;
    int inLength = inorder.length;
    for (int j = 0; j < inLength; j++) {
        inMap.put(inorder[j], j);
    }
    return buildTreeNode(preorder, 0, preLength - 1, inorder, 0, inLength - 1);
}

private TreeNode buildTreeNode(int[] preorder, int preLeft, int preRight,
                               int[] inorder, int inLeft, int inRight) {
    if (preLeft > preRight) {
        return null;
    }
    TreeNode head = new TreeNode(preorder[preLeft]);
    int inOrderIndex = inMap.get(preorder[preLeft]);
    int leftSize = inOrderIndex - inLeft;

    head.left = buildTreeNode(preorder, preLeft + 1, preLeft + leftSize,
            inorder, inLeft, inLeft + leftSize - 1);
    head.right = buildTreeNode(preorder, preLeft + leftSize + 1, preRight,
            inorder, inOrderIndex + 1, inRight);

    return head;
}

3. 从中序与后序遍历序列构造二叉树:根据一棵树的中序遍历与后序遍历构造二叉树。

例如,给出中序遍历 inorder = [9,3,15,20,7] 后序遍历postorder=[9,15,7,20,3] 返回如下的二叉树
在这里插入图片描述

private Map<Integer, Integer> inorderMap = new HashMap<>();

public TreeNode buildTree(int[] inorder, int[] postorder) {
    int length = inorder.length;
    for (int i = 0; i < length; i++) {
        inorderMap.put(inorder[i], i);
    }
    return buildTree(inorder, postorder, 0, length - 1,
            0, length - 1);
}

public TreeNode buildTree(int[] inorder, int[] postorder, int inorderLeft, int inorderRight,
                          int postorderLeft, int postorderRight) {
    if (inorderLeft > inorderRight || postorderLeft > postorderRight) {
        return null;
    }
    TreeNode head = new TreeNode(postorder[postorderRight]);

    int inorderHeadIndex = inorderMap.get(postorder[postorderRight]);

    int leftCount = inorderHeadIndex - inorderLeft;

    head.left = buildTree(inorder, postorder, inorderLeft, inorderLeft + leftCount - 1,
            postorderLeft, postorderLeft + leftCount - 1);

    head.right = buildTree(inorder, postorder, inorderHeadIndex + 1, inorderRight,
            postorderLeft + leftCount, postorderRight - 1);

    return head;
}

4. 翻转二叉树。

示例:
输入:
在这里插入图片描述
输出:
在这里插入图片描述

public TreeNode invertTree(TreeNode root) {
    if (root == null) {
        return null;
    }
    TreeNode temp = root.left;
    root.left = root.right;
    root.right = temp;

    invertTree(root.left);
    invertTree(root.right);

    return root;
}

5. 合并二叉树:给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。

示例:
输入Tree1:
在这里插入图片描述
输入Tree2:
在这里插入图片描述
输出:
合并后的树:
在这里插入图片描述

public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
        if (root1 == null && root2 == null) {
            return null;
        }
        int value1 = root1 == null ? 0 : root1.val;
        int value2 = root2 == null ? 0 : root2.val;
        TreeNode treeNode = new TreeNode(value1 + value2);
        treeNode.left = mergeTrees(root1 == null ? null : root1.left, root2 == null ? null : root2.left);
        treeNode.right = mergeTrees(root1 == null ? null : root1.right, root2 == null ? null : root2.right);

        return treeNode;
    }

6. 二叉树的直径:给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。

示例:
给定二叉树
在这里插入图片描述
返回3,它的长度是路径[4,2,1,3]或者[5,2,1,3]

int maxLength = Integer.MIN_VALUE;
public int diameterOfBinaryTree(TreeNode root) {
    calLength(root);
    return maxLength;
}
private int calLength(TreeNode node) {
    if (node == null) {
        return 0;
}
    int left = calLength(node.left);
    int right = calLength(node.right);
    maxLength = Math.max(left + right, maxLength);
    return Math.max(left, right) + 1;
}

7. 平衡二叉树:给定一个二叉树,判断它是否是高度平衡的二叉树。本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。

示例:
在这里插入图片描述
输入:root = [3,9,20,null,null,15,7]
输出:true
方法一:自顶向下的递归

public boolean isBalanced(TreeNode root) {

    if (root == null) {
        return true;
    }
    if (Math.abs(getDepth(root.left) - getDepth(root.right)) > 1) {
        return false;
    }
    return isBalanced(root.left) && isBalanced(root.right);
}

public int getDepth(TreeNode root) {
    if (root == null) {
        return 0;
    }
    return Math.max(getDepth(root.left), getDepth(root.right)) + 1;
}

方法二:自底向上的递归

public boolean isBalanced(TreeNode root) {
    return height(root) >= 0;
}

public int height(TreeNode root) {
    if (root == null) {
        return 0;
    }
    int leftHeight = height(root.left);
    int rightHeight = height(root.right);
    if (leftHeight == -1 || rightHeight == -1 || Math.abs(leftHeight - rightHeight) > 1) {
        return -1;
    } else {
        return Math.max(leftHeight, rightHeight) + 1;
    }
}

8. 二叉树的最小深度:给定一个二叉树,找出其最小深度。最小深度是从根节点到最近叶子节点的最短路径上的节点数量。说明:叶子节点是指没有子节点的节点。

(此题关键点在于某个节点的左节点或右节点为空的情况)
示例:
在这里插入图片描述
输入:root = [3,9,20,null,null,15,7]
输出:2

public int minDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int left = minDepth(root.left);
        int right = minDepth(root.right);
        if (left == 0 || right == 0) {
            return Math.max(left, right) + 1;
        }
        return Math.min(left, right) + 1;
}

9. 另一棵树的子树:给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false 。二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。

示例:
在这里插入图片描述
输入:root = [3,4,5,1,2], subRoot = [4,1,2]
输出:true

public boolean isSubtree(TreeNode s, TreeNode t) {
    return dfs(s, t);
}

public boolean dfs(TreeNode s, TreeNode t) {
    if (s == null) {
        return false;
    }
    return check(s, t) || dfs(s.left, t) || dfs(s.right, t);
}

public boolean check(TreeNode s, TreeNode t) {
    if (s == null && t == null) {
        return true;
    }
    if (s == null || t == null || s.val != t.val) {
        return false;
    }
    return check(s.left, t.left) && check(s.right, t.right);
}

若不限定子树包括 tree 的某个节点的所有后代节点,如下示例也满足的话:
在这里插入图片描述
输入:root = [3,4,5,1,2,null,null,null,null,0], subRoot = [4,1,2]
输出:true

private static TreeNode originSubTree;

public static boolean isSubTree(TreeNode treeNode1, TreeNode treeNode2) {
    if (treeNode1 == null || treeNode2 == null) {
        return false;
    }
    originSubTree = treeNode2;
    return validTree1(treeNode1, treeNode1, treeNode2);
}

private static boolean validTree1(TreeNode mainTree, TreeNode curMainTree, TreeNode subTree) {
    if (subTree == null) {
        return true;
    }
    if (curMainTree == null) {
        return false;
    }
    if (curMainTree.val == subTree.val) {
        return validTree1(mainTree, curMainTree.left, subTree.left)
                && validTree1(mainTree, curMainTree.right, subTree.right);
    }
    return validTree1(mainTree.left, mainTree.left, originSubTree)
            || validTree1(mainTree.right, mainTree.right, originSubTree);
}

10. 路径总和:给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。叶子节点 是指没有子节点的节点。

示例:
在这里插入图片描述
输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出: true

方法1:递归

public boolean hasPathSum(TreeNode root, int targetSum) {
    if (root == null) {
        return false;
    }
    if (root.left == null && root.right == null) {
        return targetSum == root.val;
    }
    return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val);
}

方法2:广度优先搜索

public boolean hasPathSum(TreeNode root, int sum) {
    if (root == null) {
        return false;
    }
    Queue<TreeNode> queNode = new LinkedList<TreeNode>();
    Queue<Integer> queVal = new LinkedList<Integer>();
    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;
}

11. 路径总和 II:给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。叶子节点 是指没有子节点的节点。

示例:
在这里插入图片描述
输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:[[5,4,11,2], [5,8,4,5]]

List<List<Integer>> resultList = new ArrayList<>();
List<Integer> tempList = new ArrayList<>();

public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
    dealTree(root, targetSum);
    return resultList;
}

private void dealTree(TreeNode root, int targetSum) {
    if (root == null) {
        return;
    }
    targetSum = targetSum - root.val;
    tempList.add(root.val);
    if (root.left == null && root.right == null && targetSum == 0) {
        resultList.add(new ArrayList<>(tempList));
    }
    dealTree(root.left, targetSum);
    dealTree(root.right, targetSum);
    tempList.remove(tempList.size() - 1);
}

12. 二叉树的所有路径:给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。叶子节点 是指没有子节点的节点。

示例:
在这里插入图片描述
输入:root = [1,2,3, null,5]
输出:[“1->2->5”,“1->3”]

public List<String> binaryTreePaths(TreeNode root) {
    List<String> result = new ArrayList<>();
    constructPaths(root, result, "");
    return result;
}

private void constructPaths(TreeNode root, List<String> result, String str) {
    if (root != null) {
        StringBuffer sb = new StringBuffer(str);
        sb.append(root.val);
        if (root.left == null && root.right == null) {
            result.add(sb.toString());
        } else {
            sb.append("->");
            constructPaths(root.left, result, sb.toString());
            constructPaths(root.right, result, sb.toString());
        }
    }
}

13. 二叉树展开为链表:给你二叉树的根结点 root ,请你将它展开为一个单链表:展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。展开后的单链表应该与二叉树 先序遍历顺序相同。

示例:
在这里插入图片描述
输入:root = [1,2,5,3,4,null,6]
输出:[1,null,2,null,3,null,4,null,5,null,6]

public void flatten(TreeNode root) {
    TreeNode curr = root;
    while (curr != null) {
        if (curr.left != null) {
            TreeNode next = curr.left;
            TreeNode predecessor = next;
            while (predecessor.right != null) {
                predecessor = predecessor.right;
            }
            predecessor.right = curr.right;
            curr.left = null;
            curr.right = next;
        }
        curr = curr.right;
    }
}

补充说明 :
示例代码:

TreeNode node1 = new TreeNode(1);
TreeNode node2 = new TreeNode(2);
TreeNode node3 = new TreeNode(3);
TreeNode node4 = new TreeNode(4);
TreeNode node5 = new TreeNode(5);
TreeNode node6 = new TreeNode(6);
TreeNode node7 = new TreeNode(7);
node1.setLeft(node2);
node1.setRight(node3);

node2.setLeft(node4);
node2.setRight(node5);

node3.setLeft(node6);
node3.setRight(node7);

对应树结构:
在这里插入图片描述
此题的关键解法在于需要把非父子节点的两节点,连接起来。若不能进入while (predecessor.right != null) {判断条件内,此种情况curr
类似于图中的节点2,需要连接4,5,若能进入,此种情况curr类似于图中的节点1,需要连接5,3

14. 左叶子之和。计算给定二叉树的所有左叶子之和。

示例:
在这里插入图片描述

在这个二叉树中,有两个左叶子,分别是9和15,所以返回24

private int sum = 0;
public int sumOfLeftLeaves(TreeNode root) {
    getLeftSum(root);
    return sum;
}

private void getLeftSum(TreeNode root) {
    if(root ==null){
        return;
    }
    if(root.left != null && root.left.left == null && root.left.right == null){
        sum = sum + root.left.val;
    }else{
        getLeftSum(root.left);
    }
    getLeftSum(root.right);
}

15. 二叉树的后序遍历(栈的方式实现)。

示例
输入:[1,null,2,3]
在这里插入图片描述
输出:[3,2,1]

public static List<Integer> postorderTraversal1(TreeNode root) {
    List<Integer> res = new ArrayList<Integer>();
    if (root == null) {
        return res;
    }
    Deque<TreeNode> stack = new LinkedList<TreeNode>();
    TreeNode prev = null;
    while (root != null || !stack.isEmpty()) {
        while (root != null) {
            stack.push(root);
            root = root.left;
        }
        root = stack.pop();
        if (root.right == null || root.right == prev) {
            res.add(root.val);
            prev = root;
            root = null;
        } else {
            stack.push(root);
            root = root.right;
        }
    }
    return res;
}

补充说明:
说明示例:
在这里插入图片描述
在这里插入图片描述

16. 树的子结构。输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)B是A的子结构, 即 A中有出现和B相同的结构和节点值。

示例:
树A:
在这里插入图片描述
树B:
在这里插入图片描述
返回true, 因为B与A的一个子树拥有相同的结构和节点值。

public static boolean isSubStructure(TreeNode A, TreeNode B) {
    if(A == null || B == null){
        return false;
    }
    return verifyNode(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B);
}

private static boolean verifyNode(TreeNode main, TreeNode sub) {
    if(sub == null){
        return true;
    }
    if(main == null){
        return false;
    }
    if(main.val == sub.val){
        return verifyNode(main.left, sub.left) && verifyNode(main.right, sub.right);
    }else{
        return false;
    }
}

17. 链表中倒数第k个节点:输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。

示例:
给定一个链表:1->2->3->4->5, 和k=2. 返回链表 4->5

双指针法:

public ListNode getKthFromEnd(ListNode head, int k){
    ListNode quick = head;
    ListNode slow = head;
    while(k > 0){
        quick = quick.next;
        k--;
    }
    while(quick != null){
        quick = quick.next;
        slow = slow.next;
    }
    return slow;
}

18. 不同的二叉搜索树:给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。

示例:
在这里插入图片描述
输入:n = 3
输出:5

解法1:

public int numTrees(int n) {
    int [] fn = new int[n+1];
    fn[0] =1;
    fn[1] =1;
    for(int i=2;i<=n;i++){
        for(int j=1;j<=i;j++){
            fn[i] += fn[j-1] * fn[i-j];
        }
    }
    return fn[n];
}

补充说明:
在这里插入图片描述
解法2:

Map<Integer, Integer> resultMap = new HashMap();
public int numTrees(int n) {
    resultMap.put(0, 1);
    resultMap.put(1, 1);
    return getRes(n);
}
private int getRes(int n) {
    if (resultMap.containsKey(n)) {
        return resultMap.get(n);
    }
    int temp = 0;
    for (int i = 1; i <= n; i++) {
        int cur = getRes(i - 1) * getRes(n - i);
        temp = temp + cur;
    }
    resultMap.put(n, temp);
    return temp;
}

19. 打家劫舍 III 在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。

示例:
输入:[3,2,3,null,3,null,1]
在这里插入图片描述
输出: 7
解释:小偷一晚能够偷取的最高金额= 3 + 3 + 1 = 7.

public int rob(TreeNode root) {
    int[] rootStatus = dfs(root);
    return Math.max(rootStatus[0], rootStatus[1]);
}

public int[] dfs(TreeNode node) {
    if (node == null) {
        return new int[]{0, 0};
    }
    int[] l = dfs(node.left);
    int[] r = dfs(node.right);
    int selected = node.val + l[1] + r[1];
    int notSelected = Math.max(l[0], l[1]) + Math.max(r[0], r[1]);
    return new int[]{selected, notSelected};
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值