【算法总结】二叉树常见算法题目及解题思路汇总

二叉树算法总结

主要解决思路:

  • 递归
  • 自底向上分治
  • 栈或队列

常见算法题

二叉树结构定义如下

 public class TreeNode {
     int val;
     TreeNode left;
     TreeNode right;
     TreeNode(int x) { val = x; }
 }

1、前序遍历 LeetCode144

给定一个二叉树,返回它的 前序 遍历。

递归解法

public List<Integer> preorderTraversal(TreeNode root) {
	ArrayList list = new ArrayList<Integer>();
  preorderHelper(root, list);
  return list;
}
public void preorderHelper(TreeNode root, ArrayList list){
  if (root == null)return;
  list.add(root.val);
  preorderHelper(root.left, list);
  preorderHelper(root.right, list);
}

**注意点:**注意判空

迭代解法:

压入栈

public List<Integer> preorderTraversal(TreeNode root) {
	ArrayList<Integer> list = new ArrayList<Integer>();
  Stack<TreeNode> stack = new Stack<TreeNode>();
  if (root == null)return list;
  stack.push(root);
  while(!stack.isEmpty()){
    TreeNode visit = stack.pop();
    list.add(visit.val);
    if (root.right != null)stack.push(root.right);
    if (root.left != null)stack.push(root.left);
  }
  return list;
}

序列化二叉树 #297

你可以将以下二叉树:

    1
   / \
  2   3
     / \
    4   5

序列化为 “[1,2,3,null,null,4,5]”

public String serialize(TreeNode root) {
  StringBuilder res = helper(root, new StringBuilder());
  return res.toString();
}

private StringBuilder helper(TreeNode node, StringBuilder sb){
  if(node == null){
    sb.append("null,");
    return sb;
  }
  sb.append(node.val);
  sb.append(",");
  sb = helper(node.left, sb);
  sb = helper(node.right, sb);
  return sb; 
}

    // Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
  String[] array = data.split(",");
  List<String> list = new LinkedList<>(Arrays.asList(array));
  return rebuildTree(list);
}
private TreeNode rebuildTree(List<String> list){
  if(list.get(0).equals("null")){
    list.remove(0);
    return null;
  }
  TreeNode node = new TreeNode(Integer.valueOf(list.get(0)));
  list.remove(0);
  node.left = rebuildTree(list);
  node.right = rebuildTree(list);
  return node;
}

2、中序遍历94

递归方法和前序遍历类似,只是优先展示左子树的值

public List<Integer> inorderTraversal(TreeNode root) {
	ArrayList list = new ArrayList<Integer>();
  inorderHelper(root, list);
  return list;
}
public void inorderHelper(TreeNode root, ArrayList list){
  if (root == null)return;
  inorderHelper(root.left, list);
  list.add(root.val);
  inorderHelper(root.right, list);
}

迭代方法

public List<Integer> inorderTraversal(TreeNode root) {
	ArrayList<Integer> list = new ArrayList<Integer>();
  Stack<TreeNode> stack = new Stack<TreeNode>();
  TreeNode cur = root;
  while (cur != null || !stack.isEmpty()){
    while (cur != null){
      stack.push(cur);
      cur = cur.left;
    }
    cur = stack.pop();
    list.add(cur.val);
		cur = cur.right;
  }
  return list;
}

3、后续遍历#145

public List<Integer> postorderTraversal(TreeNode root) {
  ArrayList list = new ArrayList<Integer>();
  postorderHelper(root, list);
  return list;
}
public void postorderHelper(TreeNode root, ArrayList list){
  if (root == null)return;
  postorderHelper(root.left, list);
  postorderHelper(root.right, list);
  list.add(root.val);
}

注意点:

4、层序遍历 #102

即逐层地,从左到右访问所有节点

Input:

    3
   / \
  9  20
    /  \
   15   7

Output: [[3],[9,20],[15,7]]

ArrayList<ArrayList<Integer>> Print(BinaryTreeNode pRoot) {
  List<List<Integer>> res = new ArrayList<>();
  if (root == null) return res;

  ArrayList<Integer> list = new ArrayList<Integer>();
  Queue<TreeNode> queue = new LinkedList<TreeNode>();
  queue.offer(root);
  int start = 0, end = 1;
  while (!queue.isEmpty()) {
    //先进先出,从左到右
    TreeNode node = queue.poll();
    list.add(node.val);
    start++;
    if (node.left != null) queue.offer(node.left);
    if (node.right != null) queue.offer(node.right);
    //行控制 eg:end=1->2->4
    if (start == end) {
      end = queue.size();
      start = 0;
      res.add(list);
      list = new ArrayList<Integer>();
    }
  }
     return res;
}

5、打印Z字型 #面试题32

请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。

使用两个栈,其中一个栈(stack2)处理从左到右打印,另一个栈处理从右到左打印,stack2先放左子树再放右子树到stack中,这样stack先取右子树再取左子树,同理stack入栈顺序相反,完成Z字型打印

public List<List<Integer>> levelOrder(TreeNode root) {
  List<List<Integer>> res = new ArrayList<>();
  ArrayList<Integer> list = new ArrayList<Integer>();
  if (root == null) return res;
  int start = 0, end = 1;
  Stack<TreeNode> stack = new Stack<>();
  Stack<TreeNode> stack2 = new Stack<>();
  stack2.push(root);
  while (!stack.isEmpty() || !stack2.isEmpty()) {
    while (!stack2.isEmpty()) {
      TreeNode qNode = stack2.pop();
      start++;
      list.add(qNode.val);
      if (qNode.left != null) stack.push(qNode.left);
      if (qNode.right != null) stack.push(qNode.right);
      if (start == end) {
        end = stack.size();
        start = 0;
        res.add(list);
        list = new ArrayList<Integer>();
      }
    }
    while (!stack.isEmpty()) {
      TreeNode sNode = stack.pop();
      start++;
      list.add(sNode.val);
      if (sNode.right != null) stack2.push(sNode.right);
      if (sNode.left != null) stack2.push(sNode.left);
      if (start == end) {
        end = stack2.size();
        start = 0;
        res.add(list);
        list = new ArrayList<Integer>();
      }
    }
  }

  return res;
}

5、包含子树

树A

    3
   / \
  9  20
    /  \
   15   7

子树B

    3         20
   / \         \
  9  20         7
public boolean isSubStructure(TreeNode A, TreeNode B) {
  if (A == null || B == null)return false;
  //从根开始匹配 左子树开始 右子树开始 
  return dfs(A, B) || isSubStructure(A.left,B) || isSubStructure(A.right,B);
}

public boolean dfs(TreeNode A, TreeNode B){
  if(B == null) return true;
  if(A == null) return false;
  return A.val == B.val && dfs(A.left, B.left) && dfs(A.right, B.right);
}

6、镜像树 #面试题27
输入:

    3
   / \
  9  20
    /  \
   15   7   

输出:

    3
   / \
  20  9
 /  \
7   15  
public TreeNode mirrorTree(TreeNode root) {
  if (root == null) return root;
  //叶子节点
  if (root.left == null && root.right == null) return root;
  //左右互换
  TreeNode temp = root.left;
  root.left = root.right;
  root.right = temp;
  if (root.left != null) mirrorTree(root.left);
  if (root.right != null) mirrorTree(root.right);
  return root;
}

6、对称树 #面试题28

如果一棵二叉树和它的镜像一样,那么它是对称的

public boolean isSymmetric(TreeNode root) {
  return root == null ? true : isSymmetric(root.left, root.right);
}

public boolean isSymmetric(TreeNode L, TreeNode R){
  if(L == null && R == null)return true;
  //左右子树一个存在一个不存在,或左右子树值不相等
  if(L == null || R == null || L.val != R.val){
    return false;
  }
  return isSymmetric(L.left,R.right) && isSymmetric(L.right,R.left);
}

7、路径和 #112

给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。

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

变形:给定一棵二叉树,其中每个节点都含有一个整数数值(该值或正或负)。设计一个算法,打印节点数值总和等于某个给定值的所有路径的数量。注意,路径不一定非得从二叉树的根节点或叶节点开始或结束。

Input : 22

          5
         / \
        4   8
       /   / \
      11  13  4
     /  \    / \
    7    2  5   1

Output : 3 [5,4,11,2]、[5,8,4,5]、[4,11,7]

public int pathSum(TreeNode root, int sum) {
  if(root == null)return 0;
  // 包含子树
  return helper(root, sum) + pathSum(root.left, sum) + pathSum(root.right, sum);
}

private int helper(TreeNode node, int sum){
  if (null == node){
    return 0;
  }
  sum -= node.val;
  int count = sum == 0 ? 1 : 0;
  count += helper(node.left, sum);
  count += helper(node.right, sum);
  return count;
}

8.1树最小深度

给定一个二叉树,找出其最小深度。

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

  • 叶子节点的定义是左孩子和右孩子都为 null 时叫做叶子节点
  • 当 root 节点左右孩子都为空时,返回 1
  • 当 root 节点左右孩子有一个为空时,返回不为空的孩子节点的深度
  • 当 root 节点左右孩子都不为空时,返回左右孩子较小深度的节点值
public int minDepth(TreeNode root) {
  if (root == null) return 0;
  if (root.left == null) return minDepth(root.right) + 1;
  if (root.right == null) return minDepth(root.left) + 1;
  int leftDepth = minDepth(root.left) + 1;
  int rightDepth = minDepth(root.right) + 1;
  return leftDepth < rightDepth ? leftDepth : rightDepth;
}

8.2树最大深度

public int treeMaxDepth(BinaryTreeNode root) {
  if (root == null) return 0;
  //if (root.left == null && root.right == null) return 1;
  int left = treeMaxDepth(root.left) + 1;
  int right = treeMaxDepth(root.right) + 1;
  return left > right ? left : right;
}

根判空 —> 左右子树判空 —> 递归加1 —> 对比最大深度

变形:给定一个二叉树,判断它是否是高度平衡的二叉树。

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

public boolean isBalanced(TreeNode root) {
  //空树
  if(root == null)return true;
  return Math.abs(getHeight(root.left) - getHeight(root.right)) <= 1
    && isBalanced(root.left) 
    && isBalanced(root.right);
}

private int getHeight(TreeNode node){
  if(node == null)return 1;
  return 1 + Math.max(getHeight(node.left), getHeight(node.right));
}


9.树的宽度

树的宽度是所有层中的最大宽度。每一层的宽度被定义为两个端点(该层最左和最右的非空节点,两端点间的null节点也计入长度)之间的长度。

9、第K大节点 #面试题54

给定一棵二叉搜索树,请找出其中第k大的节点。

输入: root = [3,1,4,null,2], k = 1
   3
  / \
 1   4
  \
   2
输出: 4

二叉搜索树特点是左子树值小于根节点值,右子树值大于根节点值,利用中序遍历思想,比如判断一个树是否为二叉搜索树也可用这种思想

public int kthLargest(TreeNode root, int k) {
  if(root == null)return 0;
  ArrayList<Integer> list = new ArrayList<>();
  inorderHelper(root, list);
  return list.get(list.size() - k);
}

public void inorderHelper(TreeNode node, ArrayList list){
  if(node == null)return;
  inorderHelper(node.left, list);
  list.add(node.val);
  inorderHelper(node.right, list);
}

10、从前序和中序遍历序列中构建二叉树 #剑指7

前序遍历 preorder = [3,9,20,15,7]
中序遍历 inorder = [9,3,15,20,7]
    3
   / \
  9  20
    /  \
   15   7
public TreeNode buildTree(int[] preorder, int[] inorder) {
  if (preorder.length == 0 || inorder.length == 0) return null;
  int rootVal = preorder[0];
  TreeNode root = new TreeNode(rootVal);
  for (int i = 0; i < preorder.length; i++) {
    if (inorder[i] == preorder[0]) {
      //包括前面不包括后面
      root.left = buildTree(Arrays.copyOfRange(preorder, 1, i + 1), Arrays.copyOfRange(inorder, 0, i ));
      root.right = buildTree(Arrays.copyOfRange(preorder, i + 1, preorder.length), Arrays.copyOfRange(inorder, i + 1, inorder.length));
      break;
    }
  }
  return root;
}

11-1、给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。

* 如果当前结点t 大于结点p、q,说明p、q都在t 的左侧,所以它们的共同祖先必定在t 的左子树中,故从t 的左子树中继续查找;
* 如果当前结点t 小于结点p、q,说明p、q都在t 的右侧,所以它们的共同祖先必定在t 的右子树中,故从t 的右子树中继续查找;
* 如果当前结点t 满足 p <t < q,说明p和q分居在t 的两侧,故当前结点t 即为最近公共祖先;
* 而如果p是q的祖先,那么返回q的父结点,同理,如果q是p的祖先,那么返回p的父结点。
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null || p == null || q == null) return root;
        if (root.val > p.val && root.val > q.val) {
            return lowestCommonAncestor(root.left, p, q);
        } else if (root.val < p.val && root.val < q.val) {
            return lowestCommonAncestor(root.right, p, q);
        } else {
            return root;
        }
    }

11-2、给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

    public TreeNode lowestCommonAncestor2(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null || p == root || q == root) return root;
        TreeNode left = lowestCommonAncestor2(root.left, p, q);
        TreeNode right = lowestCommonAncestor2(root.right, p, q);
        if (left == null) {
            return right;
        }
        if (right == null) {
            return left;
        }
        return root;
    }

12、给定一个二叉树,原地将它展开为一个单链表。#Leetcode 114

输入:

    1
   / \
  2   5
 / \   \
3   4   6

输出:

1
 \
  2
   \
    3
     \
      4
       \
        5
         \
          6

先将二叉树前序遍历形成list,然后构成新的树

    public void flatten(TreeNode root) {
        List<TreeNode> list = new ArrayList<TreeNode>();
        preorderTraversal(root, list);
        int size = list.size();
        for (int i = 1; i < size; i++) {
            TreeNode prev = list.get(i - 1), curr = list.get(i);
            //左子树都是空
            prev.left = null;
            prev.right = curr;
        }
    }

    public void preorderTraversal(TreeNode root, List<TreeNode> list) {
        if (root != null) {
            list.add(root);
            preorderTraversal(root.left, list);
            preorderTraversal(root.right, list);
        }
    }

13、完全二叉树判断 #Leetcode 958

给定一个二叉树,确定它是否是一个完全二叉树

若设二叉树的深度为 h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。(注:第 h 层可能包含 1~ 2h 个节点。)

输入:[1,2,3,4,5,6]
输出:true
解释:最后一层前的每一层都是满的(即,结点值为 {1} 和 {2,3} 的两层),且最后一层中的所有结点({4,5,6})都尽可能地向左。

    public boolean isCompleteTree(TreeNode root) {
        if (root == null) return true;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        boolean flag = false;
        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
            if (node == null) {
                flag = true;
                continue;
            }
            if (flag) {
                return false;
            }
            queue.offer(node.left);
            queue.offer(node.right);

        }
        return true;
    }

14、二叉搜索树的后序遍历序列

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。

输入: [1,3,2,6,5]
输出: true

思路:数组最后一位是二叉树的根,前半部分都比根值小,后半部分比根值大,且每个部分都按照这种方式判断

注意:1、停止条件;2、记录过程中左右子树分割的下标;3、所有前半部分都比根值小,后半部分比根值大

    public boolean verifyPostorder(int[] postorder) {
				return recur(postorder, 0, postorder.length - 1);
    }

		public boolean recur(int[] postorder, int i, int j){
      	if(i >= j)return true;
      	int p = i;
      	while(postorder[j] > postorder[p])p++;
      	int m = p;
      	while(postorder[j] < postorder[p])p++;
      	return p == j && recur(postorder, i, m-1) && recur(postorder, m, j - 1);
    }

15、N叉树的层序遍历 LeetCode 429

给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。

[
     [1],
     [3,2,4],
     [5,6]
]
    public List<List<Integer>> levelOrder(Node root) {
        
    }

16、合并二叉树 617
给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。

你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。

输入: 
	Tree 1                     Tree 2                  
          1                         2                             
         / \                       / \                            
        3   2                     1   3                        
       /                           \   \                      
      5                             4   7                  
输出: 
合并后的树:
	     3
	    / \
	   4   5
	  / \   \ 
	 5   4   7

    public static TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
        if (root1 == null) return root2;
        if (root2 == null) return root1;
        TreeNode merge = new TreeNode(root1.val + root2.val);
        merge.left = mergeTrees(root1.left, root2.left);
        merge.right = mergeTrees(root1.right, root2.right);
        return merge;
    }

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/merge-two-binary-trees
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值