面试必考二叉树
原文链接:https://labuladong.github.io/algo/2/19/21/
其实力扣刷题不知道怎么入手可以先刷二叉树题目,因为很多经典算法(回溯,动规,分治)都是树的问题。树的问题逃不开递归遍历框架:
/* 二叉树遍历框架 */
void traverse(TreeNode root) {
// 前序遍历
traverse(root.left)
// 中序遍历
traverse(root.right)
// 后序遍历
}
1. 二叉树重要性
快速排序就是个二叉树的前序遍历,归并排序就是个二叉树的后序遍历。
快排逻辑:如果要对一个数组nums[low,…,high]排序,先要找一个分界点p,通过交换元素要使得分界点左边的元素都比nums[p]要小,右边的元素都比nums[p]要大。然后再递归寻找左边分界点和右边分界点。快排框架如下:
void sort(int[] nums, int lo, int hi) {
/****** 前序遍历位置 ******/
// 通过交换元素构建分界点 p
int p = partition(nums, lo, hi);
/************************/
sort(nums, lo, p - 1);
sort(nums, p + 1, hi);
}
先构造分界点,然后去左右子数组构造分界点,和二叉树先序遍历一样。
归并逻辑:如果要对一个数组nums[low,…,high]排序,先对nums[low,…,mid]排序,再对nums[mid+1,…,high]排序,再两者合并。代码框架:
void sort(int[] nums, int lo, int hi) {
int mid = (lo + hi) / 2;
sort(nums, lo, mid);
sort(nums, mid + 1, hi);
/****** 后序遍历位置 ******/
// 合并两个排好序的子数组
merge(nums, lo, mid, hi);
/************************/
}
二叉树的递归就是要明确函数定义,用定义推结果。明确每个节点要干的事情,明确要干的事情是在前中后哪个位置干。
2. 翻转二叉树
2.1 题目描述
翻转一颗二叉树:
输入
输出
2.2 解题思路
翻转整棵树就是交换每个节点的左右子节点,可以把交换左右子节点的代码放在前序或者后序遍历的位置。
代码
public TreeNode invertTree(TreeNode root) {
if(root==null)
return null;
TreeNode tempnode = root.left;
root.left = root.right;
root.right = tempnode;
invertTree(root.left);
invertTree(root.right);
return root;
}
3. 填充二叉树右侧指针
3.1 题目描述
输入
root = [1,2,3,4,5,6,7]
输出
[1,#,2,3,#,4,5,6,7,#]
解释
3.2 解题思路
题目的要求就是把每层二叉树的节点从左到右串起来,二叉树的问题难点在于,如何把题目的要求细化成每个节点需要做的事情。这边我们可以细化成把一个节点的左右子节点串起来,把相邻两个节点的右节点和左节点串起来。所以需要增加函数参数,传入两个节点。
public class connectNode {
public Node connect(Node root) {
if(root==null)
return null;
connectNodes(root.left,root.right);
return root;
}
public void connectNodes(Node node1,Node node2){
if(node1==null||node2==null)
return;
node1.next = node2;
// 连接相同父节点的两个子节点
connectNodes(node1.left,node1.right);
// 连接跨越父节点的两个子节点
connectNodes(node1.right,node2.left);
// 连接相同父节点的两个子节点
connectNodes(node2.left,node2.right);
}
}
4. 将二叉树展开为链表
4.1 题目描述
输入
root = [1,2,5,3,4,null,6]
输出
[1,null,2,null,3,null,4,null,5,null,6]
4.2 解题思路
可以简化一下问题,如果root只有左右两个子节点,怎么拉平成一个链表?以下流程:将 root 的右子树接到左子树下方,然后将整个左子树作为右子树。注意递归框架是后序遍历,因为我们要先拉平左右子树才能进行后续操作。
代码
public class FlattenTree {
public void flatten(TreeNode root) {
if(root==null)
return;
flatten(root.left);
flatten(root.right);
TreeNode left = root.left;
TreeNode right = root.right;
root.left = null;
//将整个左子树作为右子树
root.right = left;
TreeNode temp = root;
//找到右子树的末端
while (temp.right!=null){
temp = temp.right;
}
//原来的右子树接到左子树下方
temp.right = right;
return;
}
}
5. 构造最大二叉树
5.1 题目描述
给定一个不含重复元素的整数数组 nums 。一个以此数组直接递归构建的 最大二叉树 定义如下:
- 二叉树的根是数组 nums 中的最大元素。
- 左子树是通过数组中 最大值左边部分 递归构造出的最大二叉树。
- 右子树是通过数组中 最大值右边部分 递归构造出的最大二叉树。
返回有给定数组 nums 构建的 最大二叉树 。
输入
nums = [3,2,1,6,0,5]
输出
[6,3,5,null,2,0,null,null,1]
解释
- [3,2,1,6,0,5] 中的最大值是 6 ,左边部分是 [3,2,1] ,右边部分是 [0,5] 。
- [3,2,1] 中的最大值是 3 ,左边部分是 [] ,右边部分是 [2,1] 。
- 空数组,无子节点。
- [2,1] 中的最大值是 2 ,左边部分是 [] ,右边部分是 [1] 。
- 空数组,无子节点。
- 只有一个元素,所以子节点是一个值为 1 的节点。
- [0,5] 中的最大值是 5 ,左边部分是 [0] ,右边部分是 [] 。
- 只有一个元素,所以子节点是一个值为 0 的节点。
- 空数组,无子节点。
- [3,2,1] 中的最大值是 3 ,左边部分是 [] ,右边部分是 [2,1] 。
5.2 解题思路
要明确根节点要做什么,这里根节点要做的就是把自己构造出来。遍历数组找最大值,把根节点root做出来,然后对最大值左边和右边数组做递归调用,作为根节点左右子树。
代码
public class ConstructMaxTree {
public TreeNode constructMaximumBinaryTree(int[] nums) {
int n = nums.length;
if(n==0)
return null;
//找数组中最大值
int maxVal = nums[0],index = 0;
for(int i=0;i<n;i++){
if(nums[i]>=maxVal){
maxVal = nums[i];
index = i;
}
}
TreeNode root = new TreeNode(maxVal);
//递归调用左右子树
if(index>0) {
int[] left = Arrays.copyOfRange(nums, 0, index);
root.left = constructMaximumBinaryTree(left);
}
else
root.left = null;
if(index<n-1){
int[] right = Arrays.copyOfRange(nums,index+1,n);
root.right = constructMaximumBinaryTree(right);
}
else
root.right = null;
return root;
}
}
6. 寻找重复子树
6.1 题目描述
给定一棵二叉树,返回所有重复的子树。对于同一类的重复子树,你只需要返回其中任意一棵的根结点即可。
两棵树重复是指它们具有相同的结构以及相同的结点值。
示例
6.2 解题思路
假设站在某个节点上,想知道以自己为根的子树是不是重复的,是否应该被加入结果列表中。需要知道以下两点:
1、以我为根的这棵二叉树(子树)长啥样?
2、以其他节点为根的子树都长啥样?
以该节点为根的子树长啥样——可以把二叉树进行序列化展示,例如示例1的二叉树,序列化出来:“1,2,4,#,3,2,4,#,#,#,4,#,#”(按照任意一种原则遍历即可)
以其他节点为根的子树都长啥样?——使用哈希表存贮每个节点为根的序列化结果,记录对应的数量。
代码
public class findDuplicateSubtrees652 {
Map<String,Integer> subtree = new HashMap<>();//子树序列数量
List<TreeNode> ans = new ArrayList<>();
public List<TreeNode> findDuplicateSubtrees(TreeNode root) {
findSubTree(root);
return ans;
}
//返回序列化结果
public String findSubTree(TreeNode root){
if(root==null) return "#";
String treeseq = root.val + "," + findSubTree(root.left) + "," +findSubTree(root.right);
subtree.put(treeseq,subtree.getOrDefault(treeseq,0)+1);
if (subtree.get(treeseq)==2){
ans.add(root);
}
return treeseq;
}
}