1、二叉树基础
- 二叉树的遍历模板
void traverse(TreeNode root) {
// 前序遍历
traverse(root.left)
// 中序遍历
traverse(root.right)
// 后序遍历
}
快速排序相当于二叉树的前序遍历
归并排序相当于二叉树的后续遍历
- 快速排序的框架
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);
}
- 归并排序的框架
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);
/************************/
}
1.1 递归的算法的诀窍
- 写递归算法的关键是要明确函数的「定义」是什么,然后相信这个定义,利用这个定义推导最终结果,绝不要试图跳入递归
- 写树相关的算法,先搞清楚当前root节点该做什么,然后根据函数定义递归调用子节点,递归调用会让孩子节点做相同的事情
2、二叉树例题
2.1 翻转二叉树
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode mirrorTree(TreeNode root) {
//跳出条件
if(root==null)
return null;
/**前序遍历位置 */
//交换的是节点的左右孩子树,不是具体的某一个节点
TreeNode node = root.left;
root.left = root.right;
root.right = node;
//让左右孩子继续做同样的事情
mirrorTree(root.left);
mirrorTree(root.right);
return root;
}
}
2.2 填充二叉树节点的右侧指针
/*
// Definition for a Node.
class Node {
public int val;
public Node left;
public Node right;
public Node next;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, Node _left, Node _right, Node _next) {
val = _val;
left = _left;
right = _right;
next = _next;
}
};
*/
class Solution {
public Node connect(Node root) {
//一个节点无法解决非同父问题,转接为两个节点
if(root==null)
return null;
root.next=null;
fun(root.left,root.right);
return root;
}
public static void fun(Node node1, Node node2){
if(node1==null || node2==null)
return;
//两个节点先连接
node1.next = node2;
//两个节点的,左右孩子节点连接
fun(node1.left,node1.right);
fun(node2.left,node2.right);
//两个节点的,孩子跨父连接
fun(node1.right,node2.left);
}
}
2.3 将二叉树展开为链表
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
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 p = root;
while(p.right!=null){
p = p.right;
}
p.right = right;
}
}
2.4 构造最大二叉树
- 对于每个根节点,只需要找到当前nums中的最大值和对应的索引,然后递归调用左右数组构造左右子树即可
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode constructMaximumBinaryTree(int[] nums) {
return build(nums,0,nums.length-1);
}
public static TreeNode build(int[] nums,int i,int j){
if(i>j)
return null;
//找到数组最大值和其索引
int m = max(nums,i,j);
TreeNode root = new TreeNode(nums[m]);
//递归调用左右子树
root.left = build(nums,i,m-1);
root.right = build(nums,m+1,j);
return root;
}
//求出给定数组的最大值,并返回下标
public static int max(int[] nums,int front,int rear){
int max =Integer.MIN_VALUE;
int k = -1;
for(int i=front; i<rear+1;i++){
if(max < nums[i]){
max = nums[i];
k=i;
}
}
return k;
}
}
2.5 通过前序和中序遍历结果构造二叉树
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
//以中序为主构建二叉树,通过前序依次找到根节点
return build(preorder,0,preorder.length-1,
inorder,0,inorder.length-1);
}
public static TreeNode build(int[] preorder, int i,int j,int[] inorder,int m,int n){
if(i>j)
return null;
//从前序中找到根
int val = preorder[i];
//根据根求出中序根的下标
int q = index(inorder,m,n,val);
int len = q-m;//左子树的长度
TreeNode node = new TreeNode(val);
node.left = build(preorder,i+1,i+len,inorder,m,q-1);
node.right = build(preorder,i+len+1,j,inorder,q+1,n);
return node;
}
//求出中序里根的下标
public static int index(int[] inorder,int m,int n, int val){
for(int i=m;i<=n;i++){
if(inorder[i]==val)
return i;
}
return -1;
}
}
2.6 通过后序和中序遍历结果构造二叉树
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode buildTree(int[] inorder, int[] postorder) {
return build(inorder,0,inorder.length-1,postorder,0,postorder.length-1);
}
public static TreeNode build(int[] inorder,int i,int j, int[] postorder,int m,int n){
if(m>n)
return null;
//找到第一个节点,即后续最后一个
int val = postorder[n];
//然后根据后续得到的值将中序分割,即返回该值在中序的下标
int k = index(inorder,i,j,val);
int len_left = k-i;
//创建节点并加入,再遍历左右孩子
TreeNode node = new TreeNode(val);
node.left = build(inorder,i,k-1,postorder,m,m+len_left-1);
node.right = build(inorder,k+1,j,postorder,m+len_left,n-1);
return node;
}
public static int index(int[] inorder,int i,int j,int val){
for(int p=i;p<=j;p++){
if(inorder[p]==val)
return p;
}
return -1;
}
}
2.7 寻找重复的子树
以我为根的这棵二叉树(子树)长啥样?
- 利用序列化成字符串来判断
以其他节点为根的子树都长啥样?
- 保存在map中的key中
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
//记录所有子树以及出现的次数
HashMap<String,Integer> map = new HashMap<>();
//记录重复的子树根节点
LinkedList<TreeNode> res = new LinkedList<>();
public List<TreeNode> findDuplicateSubtrees(TreeNode root) {
traverse(root);
return res;
}
public String traverse(TreeNode root){
if(root==null)
return "#";
//后序遍历逐步得到节点,组成子树
//将左右子树序列化成字符串
String left = traverse(root.left);
String right = traverse(root.right);
//后序遍历的子树表示
String subTree = left+","+right+","+root.val;
//先获得子树出现的次数,第一次出现则加入最终结果,再次数加一
int freq = map.getOrDefault(subTree,0);
if(freq==1)
res.add(root);
map.put(subTree,freq+1);
return subTree;
}
}