写树相关的算法,先搞清楚当前root节点该做什么,以及什么时候做,然后根据函数定义递归调用子节点,递归调用会让孩子节点做相同的事情。
/* 二叉树遍历框架 */ void traverse(TreeNode root) { // 前序遍历 traverse(root.left) // 中序遍历 traverse(root.right) // 后序遍历 }
快速排序的逻辑:若要对 nums[lo..hi]
进行排序,先找一个分界点 p
,通过交换元素使得 nums[lo..p-1]
都小于等于 nums[p]
,且 nums[p+1..hi]
都大于 nums[p]
,然后递归地去 nums[lo..p-1]
和 nums[p+1..hi]
中寻找新的分界点,最后整个数组被排序。
代码:
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[lo..hi]
进行排序,先对 nums[lo..mid]
排序,再对 nums[mid+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、搞清楚当前root节点该做什么以及什么时候做
2、根据函数定义递归调用子节点,递归调用会让子节点做相同的事情。
输入一个二叉树根节点root,然后把整棵树镜像翻转,比如输入的二叉树如下:
4 / \ 2 7 / \ / \ 1 3 6 9
镜像翻转后,变成了:
4 / \ 7 2 / \ / \ 9 6 3 1
我们只要把二叉树上的每一个节点的左右子节点进行交换,最后就是完全翻转之后的二叉树。
public TreeNode invertTree(TreeNode root) { if (root == null){ return null; } //root节点交换左右子节点(把交换的位置放在了前序遍历的位置) TreeNode tmp = root.left; root.left = root.right; root.right = tmp; //root节点的左右子节点递归地翻转它们各自的左右子节点 invertTree(root.left); invertTree(root.right); return root; }
给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
struct Node { int val; Node *left; Node *right; Node *next; }
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
输入:root = [1,2,3,4,5,6,7] 输出:[1,#,2,3,#,4,5,6,7,#] 解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。序列化的输出按层序遍历排列,同一层节点由 next 指针连接,'#' 标志着每一层的结束。
从题目理解,可以套用上面的二叉树框架,因为都是用root节点的左节点指向右节点,按照这种思路可以写入如下的代码:
public Node connect1(Node root) { if(root == null){ return null; } /*但是这里会导致,一个节点的右子节点和另一个节点的 左子节点相连的情况无法实现。 */ root.left.next = root.right; connect1(root.left); connect1(root.right); return root; }
但是可以看到,这里只能将同一个节点下的左右子节点相连接(4,5),对于不同父节点的右节点和左节点(比如5,6)就无法实现。
对于这种,我们可以增加函数参数,一个节点无法实现,我们可以给函数参数为两个节点,就转化为了将每两个相邻的节点链接起来。
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; } connectTwoNode(root.left,root.right); return root; } public void connectTwoNode(Node node1,Node node2){ if(node1 == null || node2 == null){ return; } node1.next = node2; connectTwoNode(node1.left,node1.right); connectTwoNode(node2.left,node2.right); connectTwoNode(node1.right,node2.left); } }
给定一个不含重复元素的整数数组 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 的节点。 - 空数组,无子节点。
从题目中可知,需要使用递归方法创建一棵最大二叉树。
1、根是数组的最大元素,那么需要从数组中找到最大元素,并把它放到根的位置。
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 TreeNode constructMaximumBinaryTree(int[] nums) { return build(nums, 0, nums.length - 1); } public TreeNode build(int[] nums, int lo,int lw){ if(lo > lw){ return null; } int index = lo; for(int i = lo; i <= lw; i++){ if(nums[i] > nums[index]){ index = i; } } TreeNode root = new TreeNode(nums[index]); root.left = build(nums, lo, index - 1); root.right = build(nums, index + 1, lw); return root; } }
给定一棵二叉树,返回所有重复的子树。对于同一类的重复子树,你只需要返回其中任意一棵的根结点即可。
两棵树重复是指它们具有相同的结构以及相同的结点值。
1 / \ 2 3 / / \ 4 2 4 / 4 下面是两个重复的子树: 2 / 4 和 4
第一步:思考对于某一个节点,它应该做什么?
对于节点2来说,需要知道以自己为根的子树是否重复,是够应该被加入到结果列表中。
第二步:要想知道以自己为根的子树是否重复,需要知道两点:
1、以自己为根的子树是什么样子的
对这个问题,需要使用后序遍历,因为得先知道自己的左右子树是什么样子,然后加上自己,就构成了一个完整的子树了。
2、以其他节点为根的子树是什么样子的
第三步:明确了需要使用后序遍历后,可以通过拼接字符串的方式将二叉树序列化,如下代码:
String traverse(TreeNode root){ //对空节点,可以用一个特殊字符表示 if(root == null){ return "#"; } //将左右子树序列化成字符串 String left = traverse(root.left); String right = traverse(root.right); //后序遍历代码位置 //将左右子树加上自己,构成一个以自己为根的二叉树 String subTree = left + "," + right + "," + root.val; return subTree; }
从上面的代码可以知道,用拼接的方式将二叉树序列化了。
第四步:怎么知道以别的节点为根的子树是什么样子?
这里借助一个外部数据结构HashMap,来记录子树,同时记录子树出现的次数,这样就可以知道是否有重复了。
代码
class Solution { HashMap<String, Integer> ans = 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 subStr = left + "," + right + "," + root.val; int feq = ans.getOrDefault(subStr,0); if(feq == 1){ res.add(root); } ans.put(subStr, feq + 1); return subStr; } }
这里用到了hashmap数据结构,HashMap是一个经常会用到的数据结构。