分治算法的核心思想:
将大问题分解为小问题,递归的将小问题解决后再将结果合并;
其实知道这些就够了,掌握这种思想关键还得多刷题:
例题如下:
例题1:给定一棵树的前序遍历 preorder 与中序遍历 inorder。请构造二叉树并返回其根节点。(力扣:105)
//现在看来递归对于一种高级算法,它更像是一种工具,这道题使用分治思想,将大问题通过递归不断化解成小问题解决
class Solution {
Map <Integer,Integer> map=new HashMap<Integer,Integer>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
//首先在这道题中最重要的就是如何在前序遍历中找到根节点后,确定其在中序遍历的位置,我们可以通过HashMap建立中序遍历节点值和其所在位置的关系
int n=preorder.length;
for(int i=0;i<inorder.length;i++){
map.put(inorder[i],i);
}
return myBuildTree(preorder,inorder,0,n-1,0,n-1);
}
public TreeNode myBuildTree(int[] preorder, int[] inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) {
if (preorder_left > preorder_right) {
return null;
}
// 在中序遍历中定位根节点
int inorder_root = map.get(preorder[ preorder_left]);
// 先把根节点建立出来
TreeNode root = new TreeNode(preorder[ preorder_left]);
// 得到左子树中的节点数目
int size_left_subtree = inorder_root - inorder_left;
// 递归地构造左子树,并连接到根节点
// 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
root.left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
// 递归地构造右子树,并连接到根节点
// 先序遍历中「从 左边界+1+左子树节点数目 开始到 右边界」的元素就对应了中序遍历中「从 根节点定位+1 到 右边界」的元素
root.right = myBuildTree(preorder, inorder, preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right);
return root;
}
}
理解:这道题难在哪里呢?首先是这个出口的问题?如果不看答案的话我是找不到出口的,其次是将大问题化为小问题是,应该进行什么操作,最后就是小问题的边界问题
例题2:给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1。
//这不就是生成最小二叉树的题吗?现在感觉生成最小平衡二叉使用分治思想反而更简单
class Solution {
public TreeNode sortedListToBST(ListNode head) {
List<Integer> list=new ArrayList<Integer> ();
while(head!=null){
list.add(head.val);
head=head.next;
}
int[] a=new int[list.size()];
for(int i=0;i<list.size();i++){
a[i]=list.get(i);
}
return tree(a,0,list.size()-1);
}
public TreeNode tree(int[] a,int left,int right){
if(left>right){
return null;
}
int mid=(left+right)/2;
TreeNode root=new TreeNode(a[mid]);
root.left=tree(a,left,mid-1);
root.right=tree(a,mid+1,right);
return root;
}
}
理解:如果你看懂了上面的那题,我相信这题对你来说,应该是小菜一碟,我记得之前我好像将一道类似的题归类到了递归那边,当时我就觉得这道题的解题思路跟汉诺塔的思路差不多,因为当时还没有接触分治思想,现在看来我还是有点东西的
**例题3:**给你二叉树的根结点 root ,请你将它展开为一个单链表:展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。展开后的单链表应该与二叉树 先序遍历 顺序相同。(力扣:114)
//解法2:我看到很多思路说使用分治疗的思想,分别将左右子树展开成链,然后将左右子链拼接起来,也挺nb的,这个思路总体来说体现了一个分治的思想
class Solution {
public void flatten(TreeNode root) {
//递归出口
if(root==null){
return;
}
//因为避免在递归过程中改变了链表结构而找不到相应的节点,所以使用后序遍历
flatten(root.left);
flatten(root.right);
//保存当前节点的右子树的位置,方便后面拼接链表
TreeNode temp=root.right;
//将左子树链表拼接到当前节点的右子树的位置
root.right=root.left;
//记得将左子树赋空
root.left=null;
//找到拼接上的左子树的最后一个节点的位置
//为什么可以随意更改root的位置,因为每一层都有对应的root,这层次root的改变对其他层次没有影响
while(root.right!=null) root=root.right;
//将右子树拼接到左子树链表的后面
root.right=temp;
//记得将左子树赋空
root.left=null;
}
}
理解:这道题在递归那片文章中也有提及,不过在那边是使用递归解决的,那种思路更加的巧妙一些,不过这种解法对于理解分治思想也很有帮助所以在这边也提一下
例题4:给定一个不含重复元素的整数数组 nums 。一个以此数组直接递归构建的 最大二叉树 定义如下:
二叉树的根是数组 nums 中的最大元素。
1.左子树是通过数组中 最大值左边部分 递归构造出的最大二叉树。
2.右子树是通过数组中 最大值右边部分 递归构造出的最大二叉树。
3.返回有给定数组 nums 构建的 最大二叉树 。(力扣:654)
理解:很典型的分治算法题,无非就是加入了一个HashMap与一个搜索最大值的函数,细节思路在代码中
// 其实这到题可以为生成二叉树的进阶版
// 思路:分治算法+HashMap
class Solution {
// 首先创建一个Map将集合中的位置与该数值建立联系
Map<Integer,Integer> map=new HashMap<Integer,Integer>();
public TreeNode constructMaximumBinaryTree(int[] nums) {
for(int i=0;i<nums.length;i++){
map.put(nums[i],i);
}
return creatTree(nums,0,nums.length-1);
}
// 创建一个获取指定范围内数组的最大值
public int maxValue(int[] nums,int start,int end){
int max=nums[start];
// 注意这里i的范围一定要包含到end
for(int i=start;i<=end;i++){
if(nums[i]>max){
max=nums[i];
}
}
return max;
}
// 创建一个生成树的函数
public TreeNode creatTree(int[] nums,int left,int right){
// 出口
if(left>right){
return null;
}
// 首先获取当前范围内的最大值
int max=maxValue(nums,left,right);
// 以当前的最大值创建根节点
TreeNode root=new TreeNode(max);
// 并且获取当前最大值得位置
int index=map.get(max);
root.left=creatTree(nums,left,index-1);
root.right=creatTree(nums,index+1,right);
return root;
}
}
例题5:在一个 m*n 的二维字符串数组中输出二叉树,并遵守以下规则:
- 行数 m 应当等于给定二叉树的高度。
- 列数 n 应当总是奇数。
- 根节点的值(以字符串格式给出)应当放在可放置的第一行正中间。根节点所在的行与列会将剩余空间划分为两部分(左下部分和右下部分)。你应该将左子树输出在左下部分,右子树输出在右下部分。左下和右下部分应当有相同的大小。即使一个子树为空而另一个非空,你不需要为空的子树输出任何东西,但仍需要为另一个子树留出足够的空间。然而,如果两个子树都为空则不需要为它们留出任何空间。
- 每个未使用的空间应包含一个空的字符串""。
- 使用相同的规则输出子树。(力扣:655)
// 这道题主要是利用了分治思想,确实是十分典型的分治思想算法
//然后树的宽度w=2^deep-1,这是一个规律,我也是看评论才知道的
class Solution {
public List<List<String>> printTree(TreeNode root) {
// 获取树的高度,求出其最大宽度,创建一个二维数组,方便使用分治思想
int deep=deep(root);
int width=(int)Math.pow(2,deep)-1;
int[][] a=new int[deep][width];
// 避免它初始值来恶心我们,将初值赋值为Integer.MAX_VALUE
for(int i=0;i<deep;i++){
Arrays.fill(a[i],Integer.MAX_VALUE);
}
// 填充数组
fill(a,0,width,root,0);
// 创建一个集合
List<List<String>> list=new ArrayList<List<String>>();
// 最后一步将数组中的元素放入到集合中去
for(int i=0;i<deep;i++){
// 每层都创建一个list保存每层的字符串
List<String> temp=new ArrayList<String>();
for(int j=0;j<width;j++){
if(a[i][j]==Integer.MAX_VALUE){
temp.add("");
}
if(a[i][j]!=Integer.MAX_VALUE){
String str=String.valueOf(a[i][j]);
temp.add(str);
}
}
// 将每层保存的字符串都放进集合
list.add(temp);
}
return list;
}
// 首先先求树的高度
public int deep(TreeNode root){
if(root==null){
return 0;
}
int left= deep(root.left);
int right= deep(root.right);
return left>right?left+1:right+1;
}
// 创建一个填充数组的分治算法
public void fill(int[][] a,int l,int r,TreeNode root,int hight){
// 递归的出口
if(root==null){
return;
}
int mid=(l+r)/2;
a[hight][mid]=root.val;
// 使用分治,分别将左子树,和右子树的节点值填充进数组
fill(a,l,mid-1,root.left,hight+1);
fill(a,mid+1,r,root.right,hight+1);
}
}
例题6:给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low, high]中。修剪树不应该改变保留在树中的元素的相对结构(即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在唯一的答案。
所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变。(力扣:669)
理解:其实这道题最大的收获应该就是对于返回值的利用,以及分治的思想
// 看了看题解,我真是个傻逼,首先这道题没有必要一个一个节点的判断,该节点是否在范围内:
// 因为当root.val<low时,root的左子树也不满足
// 当root.val>high时,root的右子树也不满足
// 然后递归的处理子树即可,其实有点分治算法的感觉
class Solution {
public TreeNode trimBST(TreeNode root, int low, int high) {
// 避免出现异常
if(root==null){
return root;
}
// 如果root.val<low,那么左子树肯定就不用看了,处理结果就为右子树的处理结果
if(root.val<low){
return trimBST(root.right, low,high) ;
}
// 如果root.val>high,那么右子树肯定就不用看了,处理结果就为左子树的处理结果
if(root.val>high){
return trimBST(root.left, low,high) ;
}
// 如果该节点正常,就判断其左右节点是否满足条件
root.left= trimBST(root.left, low,high);
root.right=trimBST(root.right, low,high);
return root;
}
}