分治算法(持续更新):

分治算法的核心思想:

将大问题分解为小问题,递归的将小问题解决后再将结果合并;
其实知道这些就够了,掌握这种思想关键还得多刷题:

例题如下:

例题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 的二维字符串数组中输出二叉树,并遵守以下规则:

  1. 行数 m 应当等于给定二叉树的高度。
  2. 列数 n 应当总是奇数。
  3. 根节点的值(以字符串格式给出)应当放在可放置的第一行正中间。根节点所在的行与列会将剩余空间划分为两部分(左下部分和右下部分)。你应该将左子树输出在左下部分,右子树输出在右下部分。左下和右下部分应当有相同的大小。即使一个子树为空而另一个非空,你不需要为空的子树输出任何东西,但仍需要为另一个子树留出足够的空间。然而,如果两个子树都为空则不需要为它们留出任何空间。
  4. 每个未使用的空间应包含一个空的字符串""。
  5. 使用相同的规则输出子树。(力扣: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;
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值