[英雄星球六月集训LeetCode解题日报] 第29日 分治

这篇博客主要介绍了如何使用分治法解决LeetCode中的两道二叉树问题:654.最大二叉树和889.根据前序和后序遍历构造二叉树。对于654题,通过模拟构建过程实现;889题则利用前序和后序遍历的特点,递归地构建二叉树。同时,博主还探讨了1569题,关于重新排序子数组得到相同二叉查找树的方案数,利用BST的性质进行计算。
摘要由CSDN通过智能技术生成

日报

题目

一、 654. 最大二叉树

链接: 654. 最大二叉树

1. 题目描述

在这里插入图片描述

2. 思路分析

按照题意模拟即可。

3. 代码实现
class Solution:
    def constructMaximumBinaryTree(self, nums: List[int]) -> TreeNode:
        n = len(nums)
        def dfs(i,l,r):
            root = TreeNode(nums[i])
            m = -1
            mi = -1
            for j in range(l,i):
                if nums[j]>m:
                    m = nums[j]
                    mi = j   
            if i > l:                         
                root.left = dfs(mi,l,i-1)
            if i<r:
                m = -1
                for j in range(i+1,r+1):
                    if nums[j]>m:
                        m = nums[j]
                        mi = j                            
                root.right = dfs(mi,i+1,r)
            return root

        m = -1
        mi = -1
        for j in range(0,n):
            if nums[j]>m:
                m = nums[j]
                mi = j  
        return dfs(mi,0,n-1)

二、889. 根据前序和后序遍历构造二叉树

链接: 889. 根据前序和后序遍历构造二叉树

1. 题目描述

在这里插入图片描述

2. 思路分析
  • 我们处理一棵树的时候将这颗树分为三部分:根节点、左子树,右子树。
  • 对preoder来说,构成为[根、左子树、右子树];对postorder来说,构成为[左子树、右子树、根]。
  • 显然,这三部分在preorder和postorder里都是连续的,则有:
  • 一颗树的根节点显然是preorder[0]、postorder[-1];
  • 接下来找左右子树,左子树的根节点显然是preorder[1],然后在postorder里找到这个节点的下标i。
  • 上边我们提到左子树在postorder里是连续的,且是第一部分,那么左子树长度width就是i+1。
  • 显然剩下一部分就是右子树。
  • 创建根节点,然后dfs分别构建左右子树即可。
3. 代码实现
class Solution:
    def constructFromPrePost(self, preorder: List[int], postorder: List[int]) -> TreeNode:
        
        def dfs(preorder,postorder):
            if not preorder:
                return None
            root = TreeNode(preorder[0])
            n = len(preorder)
            if n == 1:
                return root

            width = postorder.index(preorder[1])+1   # 左子树长度
            root.left = dfs(preorder[1:1+width], postorder[:width])
            root.right = dfs(preorder[width+1:], postorder[width:n-1])
            return root

        return dfs(preorder,postorder)

在这里插入图片描述

三、 1569. 将子数组重新排序得到同一个二叉查找树的方案数

链接: 1569. 将子数组重新排序得到同一个二叉查找树的方案数

1. 题目描述

在这里插入图片描述

2. 思路分析
  • BST的性质是:这棵树所有子树满足:左子树左右节点<根节点<右子树所有节点。
  • 这题要求按数组顺序插入时,能构建同一颗BST。
  • 观察数组样例,会发现第一个插入的一定是根节点,后续插入大的就去右边,小的就去左边;
  • 大的和小的去到不同地方,那么大、小互相之间不会有影响,他们的相对顺序就是我们可以调整的。
  • 如果两个都是小于根节点的值,他们俩的插入顺序变化,左子树就会发生变化,因为先插入的是长辈;同理右子树一样。
  • 因此我们处理完左右子树后,合并的算法如下:
  • 设左子树的节点数量为a,右子树的节点数量为b,求a个小数和b个小数排列到一起的方案数,其中a、b内部的顺序不能变。
  • 这个点想了半天,最后发现其实就是C(a+b,a),即为:一共a+b个位置,选a个放左子树的东西。剩下的放b。
  • 然后就到了喜闻乐见的组合数取模问题,查了很多资料,有好几种算法计算C(n,r):
    • 当n,r<1e3,可以用n^2算法,杨辉三角帕斯卡恒等式:C(n,r) = C(n-1,r-1)+C(n-1,r). r==0时,C(n,0) = 1
      • 证明:从n中选一个基准数k,如果r里包含这个k,则是从剩下n-1个数取r-1个C(n-1,r);如果不包含,就是从剩下n-1个取r个C(n-1,r-1)。
    • 当n,r<1e5,这时n^2算法不能用了。需要用线性递推,公式大概是C(n,r) = C(n,r-1)*(n-r+1)/r ,但是这个里包含除法,需要求从乘法逆元,这里不展开了。
    • 当n,r<1e9,MOD<1e5,这时需要用卢卡斯Lucas定理,时间复杂度是plgN,但是可以用O§预先处理p以内的逆元阶乘,然后单次查询就是lgN。
    • 当n,r<1e9,MOD<1e9,那不会做了。
  • 研究半天,发现python的int自带大数,不管多少位都不溢出。而且自带组合数计算。
  • 组合数:comb(n,r)
  • 排列数:perm(n,r)
  • 阶乘:factorial(n)
  • 幂:2**65
  • 以上都不会溢出,最后直接%MOD即可。
  • Life is short,I use Python。
    有时间应该整理一下取模的模板。
3. 代码实现
class Solution:
    def numOfWays(self, nums: List[int]) -> int:
        MOD=10**9+7
        def dfs(nums):
            if not nums:
                return 1
            l,r = [],[]
            for i in range(1,len(nums)):
                if nums[i] < nums[0]:
                    l.append(nums[i])
                else:
                    r.append(nums[i])
            return comb(len(l)+len(r),len(r)) * dfs(l) *dfs(r) % MOD
        return (dfs(nums) + MOD - 1)%MOD
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值