写递归算法的秘诀:
写递归算法的关键是要明确函数的「定义」是什么
然后相信这个定义,利用这个定义推导最终结果,绝不要试图跳入递归。
怎么理解呢,我们用一个具体的例子来说,比如说让你计算一棵二叉树共有几个节点:
// 定义:count(root) 返回以 root 为根的树有多少节点
int count(TreeNode root) {
// base case
if (root == null) return 0;
// 自己加上子树的节点数就是整棵树的节点数
return 1 + count(root.left) + count(root.right);
}
root本身就是一个节点,加上左右子树的节点数就是以root为根的树的节点总数。
左右子树的节点数怎么算?其实就是计算根为root.left和root.right两棵树的节点数呗,按照定义,递归调用count函数即可算出来。
写树相关的算法,简单说就是,先搞清楚当前root节点该做什么,然后根据函数定义递归调用子节点,递归调用会让孩子节点做相同的事情。
1.翻转二叉树
/**
* 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 invertTree(TreeNode root) {
if(root==null) return root;
//前序遍历:先处理根节点在,再去遍历处理左右子节点
TreeNode temp=root.left;
root.left=root.right;
root.right=temp;
invertTree(root.left);
invertTree(root.right);
return root;
}
}
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;
dfs(root.left,root.right);
return root;
}
public void dfs(Node node1,Node node2){
if(node1==null || node2==null){
return;
}
node1.next=node2;
dfs(node1.left,node1.right);
dfs(node1.right,node2.left);
dfs(node2.left,node2.right);
}
}
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;
// if(root.left==null) return;
flatten(root.left);
flatten(root.right);
//存储右子树
TreeNode temp=root.right;
//右指针指向左子树
root.right=root.left;
//左指针指向空
root.left=null;
//遍历到最后一个节点的位置,再进行衔接
while(root.right!=null){
root=root.right;
}
root.right=temp;
}
}
4.最大二叉树
/**
* 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) {
if(nums==null || nums.length==0){
return null;
}
//采用前序遍历
//找到数组中最大值,作为根节点
int maxValue=Integer.MIN_VALUE;
int maxIndex=-1;
for(int i=0;i<nums.length;i++){
if(nums[i]>maxValue){
maxValue=nums[i];
maxIndex=i;
}
}
TreeNode root=new TreeNode(maxValue);
root.left=constructMaximumBinaryTree(Arrays.copyOfRange(nums,0,maxIndex));
root.right=constructMaximumBinaryTree(Arrays.copyOfRange(nums,maxIndex+1,nums.length));
return root;
}
}
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) {
//前序遍历
if(preorder.length==0 || inorder.length==0){
return null;
}
return buildTreeWithIndex(preorder,inorder,0,preorder.length-1,0,inorder.length-1);
}
//再定义一个函数的原因是:想使用位置界定左右子树对应的数组范围,比较方便而且节省内存
public TreeNode buildTreeWithIndex(int[] preorder,int[] inorder,int preL,int preR, int inL,int inR){
if(preL>preR){
return null;
}
//根节点的值是前序遍历第一个位置的值
int rootValue=preorder[preL];
TreeNode root=new TreeNode(rootValue);
//在中序遍历中找到该值,并记录该值位置
int indexRootValue=-1;
for(int i=inL;i<=inR;i++){ //包含右边界
if(inorder[i]==rootValue){
indexRootValue=i;
break;
}
}
//左子树长度
int leftLength=indexRootValue-inL;
root.left=buildTreeWithIndex(preorder,inorder,preL+1,preL+leftLength,inL,inL+leftLength-1);
root.right=buildTreeWithIndex(preorder,inorder,preL+leftLength+1,preR,inL+leftLength+1,inR);
return root;
}
}
6.从中序和后序遍历序列构造二叉树
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution{
public TreeNode buildTree(int[] inorder, int[] postorder) {
if(inorder.length==0 || postorder.length==0){
return null;
}
return buildTreeWithIndex(inorder,postorder,0,inorder.length-1,0,postorder.length-1);
}
//后序遍历的最后一个值是根节点,然后在中序遍历中找到该根节点的位置,该位置左边就是左子树,右边就是右子树
public TreeNode buildTreeWithIndex(int[] inorder,int[] postorder,int inL,int inR,int postL,int postR){
//越界判断
if(postL>postR || inL>inR){
return null;
}
//重要!!!!
int rootValue=postorder[postR];
TreeNode root=new TreeNode(rootValue);
int indexRootValue=-1;
for(int i=inL;i<=inR;i++){
if(inorder[i]==rootValue){
indexRootValue=i;
break;
}
}
int leftTreeSize=indexRootValue-inL;
root.left=buildTreeWithIndex(inorder,postorder,inL,inL+leftTreeSize-1,postL,postL+leftTreeSize-1);
root.right=buildTreeWithIndex(inorder,postorder,inL+leftTreeSize+1,inR,postL+leftTreeSize,postR-1);
return root;
}
}
7.寻找重复的子树
/**
* 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> memo=new HashMap<>();
List<TreeNode> res=new ArrayList<>();
public List<TreeNode> findDuplicateSubtrees(TreeNode root) {
if(root==null) return res;
traverse(root);
return res;
}
//要想确定子树是否长的一样,得进行节点存储,使用字符串存储,因为后续对比是否相等时比较方便,然后把字符串放入hashmap中进行存储
public String traverse(TreeNode root){
if(root==null){
return "#";
}
//使用后序遍历,先存储叶子节点
String leftTree=traverse(root.left);
String rightTree=traverse(root.right);
String subTree=leftTree+","+rightTree+","+root.val;
//如果当前子树出现过,就说明存在重复子树,则将根节点root放入结果集中
//重复多次的只需要放置1次,所以限定出现频率为1时,进行放置
if(memo.getOrDefault(subTree,0)==1){
res.add(root);
}
memo.put(subTree,memo.getOrDefault(subTree,0)+1);
return subTree;
}
}