【图解算法数据结构】(四)分治算法

目录

一、剑指 Offer 07. 重建二叉树 ☆

1.1 题求

1.2 求解

1.3 解答

二、剑指 Offer 16. 数值的整数次方

2.1 题求

2.2 求解

2.3 解答

三、剑指 Offer 17. 打印从 1 到最大的 n 位数

3.1 题求

3.2 求解

3.3 解答

四、剑指 Offer 33. 二叉搜索树的后序遍历序列 ☆

4.1 题求

4.2 求解

4.3 解答

五、剑指 Offer 51. 数组中的逆序对 ☆

5.1 题求

5.2 求解

5.3 解答


一、剑指 Offer 07. 重建二叉树 ☆

1.1 题求

1.2 求解

官方说明

# 36ms - 98.80%
class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        
        # 前序遍历性质: 节点按 [ 根节点 | 左子树 | 右子树 ] 排序
        # 中序遍历性质: 节点按 [ 左子树 | 根节点 | 右子树 ] 排序

        # root: 前序遍历节点索引, left/right: 中序遍历节点索引
        def recur(root, left, right):
            if left > right:  # 索引越界, 递归终止
                return   
            
            # 根据根节点在前序遍历位置索引 root 建立当前根节点 node
            node = TreeNode(preorder[root])                       
            # i 为当前根节点 node 在中序遍历中的位置索引, 对中序遍历划分根节点、左子树、右子树
            i = dic[preorder[root]]  
            # 左子树递归 (left ~ i-1 为当前根节点 node 在中序遍历的左子树索引范围)
            node.left = recur(root+1, left, i-1)       
            # root+1:当前根节点在前序遍历的位置索引 root + 1 
            #       = 左子树根节点在前序遍历的位置索引
            
            # 右子树递归 (i+1 ~ right 为当前根节点 node 在中序遍历的右子树索引范围)
            node.right = recur(i-left + root+1, i+1, right) 
            # i-left+root+1:左子树长度 (i-left) + 当前根节点在前序遍历的位置索引 root + 1 
            #              = 右子树根节点在前序遍历的位置索引
            
            # 回溯返回当前根节点 node
            return node                                           

        # 记录中序遍历的节点值与索引的映射
        dic = {}
        for i in range(len(inorder)):
            dic[inorder[i]] = i
            
        return recur(0, 0, len(inorder)-1)

1.3 解答

参考资料:

《剑指 Offer 第二版》

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/99lxci/

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/99ljye/


二、剑指 Offer 16. 数值的整数次方

2.1 题求

2.2 求解

法一:递归

# 24ms - 99.41%
class Solution:
    def myPow(self, x: float, n: int) -> float:
        # 分解规律
        # 2^7 = (2^3)^2 * 2 = ((2^1)^2 * 2)^2 * 2
        # 2^-7 = (2^-4)^2 * 2 = ((2^-2)^2)^2 * 2 = (((2^-1)^2)^2)^2 * 2
        
        # 最小子问题
        if n == 0 or n == 1 or n == -1:
            return x ** n
        
        # 商
        a = n // 2  # 7 // 2 = 3, -7 // 2 = -4  注意区别
        # 余数
        b = n % 2   # 7 % 2 = -7 % 2 = 1
        # 子问题求解
        c = self.myPow(x, a)
        # 当前问题结果
        return c * c if b == 0 else c * c * x

法一改:递归

class Solution:
    def myPow(self, x: float, n: int) -> float:

        # 最小子问题
        if -1 <= n <= 1:
            return x ** n
        
        # 商 - 用向右移位运算符代替除 2 操作
        a = n >> 1  # n // 2  
        # 余数 - 用位运算代替求余操作
        b = n & 1  # n % 2  
        # 子问题求解
        c = self.myPow(x, a)
        # 当前问题结果
        return c * c if b == 0 else c * c * x

官方说明

# 28ms - 97.27%
class Solution:
    def myPow(self, x: float, n: int) -> float:
        if x == 0.0: 
            return 0.0

        res = 1
        if n < 0: 
            x, n = 1 / x, -n
        while n:
            if n & 1: 
                res *= x
            x *= x
            n >>= 1

        return res

2.3 解答

参考资料:

《剑指 Offer 第二版》

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/57rwmg/

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/57p2pv/


三、剑指 Offer 17. 打印从 1 到最大的 n 位数

3.1 题求

3.2 求解

法一:列表推导式

# 36ms - 92.36%
class Solution:
    def printNumbers(self, n: int) -> List[int]:
        return [i for i in range(1, 10**n)]
# 32ms - 97.31%
class Solution:
    def printNumbers(self, n: int) -> List[int]:
        return [i for i in range(1, int('1' + '0' * n))]

官方说明

class Solution:
    def printNumbers(self, n: int) -> List[int]:
        res = []
        for i in range(1, 10 ** n):
            res.append(i)
        return res

class Solution:
    def printNumbers(self, n: int) -> List[int]:
        return list(range(1, 10 ** n))

class Solution:
    def printNumbers(self, n: int) -> [int]:
        def dfs(x):
            if x == n:  # 终止条件:已固定完所有位
                res.append(''.join(num)) # 拼接 num 并添加至 res 尾部
                return
            for i in range(10):  # 遍历 0 - 9
                num[x] = str(i)  # 固定第 x 位为 i
                dfs(x + 1)  # 开启固定第 x + 1 位
        
        num = ['0'] * n  # 起始数字定义为 n 个 0 组成的字符列表
        res = []  # 数字字符串列表
        dfs(0)  # 开启全排列递归
        return ','.join(res)  # 拼接所有数字字符串,使用逗号隔开,并返回

class Solution:
    def printNumbers(self, n: int) -> [int]:
        def dfs(x):
            if x == n:
                s = ''.join(num[self.start:])
                if s != '0': res.append(s)
                if n - self.start == self.nine: self.start -= 1
                return
            for i in range(10):
                if i == 9: self.nine += 1
                num[x] = str(i)
                dfs(x + 1)
            self.nine -= 1
        
        num, res = ['0'] * n, []
        self.nine = 0
        self.start = n - 1
        dfs(0)
        return ','.join(res)

class Solution:
    def printNumbers(self, n: int) -> [int]:
        def dfs(x):
            if x == n:
                s = ''.join(num[self.start:])
                if s != '0': res.append(int(s))
                if n - self.start == self.nine: self.start -= 1
                return
            for i in range(10):
                if i == 9: self.nine += 1
                num[x] = str(i)
                dfs(x + 1)
            self.nine -= 1
        
        num, res = ['0'] * n, []
        self.nine = 0
        self.start = n - 1
        dfs(0)
        return res

3.3 解答

参考资料:

《剑指 Offer 第二版》

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/594wfg/

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5912jv/


四、剑指 Offer 33. 二叉搜索树的后序遍历序列 ☆

4.1 题求

4.2 求解

官方说明

# 24ms - 99.34%
class Solution:
    def verifyPostorder(self, postorder: List[int]) -> bool:
        def recur(i, j):
            # 递归至区间越界都未发现问题, 符合
            if i >= j: 
                return True

            # 区间左起点
            p = i  
            while postorder[p] < postorder[j]:  # 当前左子树范围
                p += 1
            # 找到第一个大于当前子树的根节点 postorder[j] 的节点索引 m
            m = p  
            while postorder[p] > postorder[j]:  # 当前右子树范围
                p += 1
            # 根节点索引 j, 左子树区间 [i, m-1], 右子树区间 [m, j-1]
            return p == j and recur(i, m-1) and recur(m, j-1)

        return recur(0, len(postorder)-1)

class Solution:
    def verifyPostorder(self, postorder: [int]) -> bool:
        stack, root = [], float("+inf")
        for i in range(len(postorder) - 1, -1, -1):
            if postorder[i] > root: return False
            while(stack and postorder[i] < stack[-1]):
                root = stack.pop()
            stack.append(postorder[i])

        return True

4.3 解答

参考资料:

《剑指 Offer 第二版》

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5vwxx5/

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5vwbf6/


五、剑指 Offer 51. 数组中的逆序对 ☆

5.1 题求

5.2 求解

法一:选择排序 

# 超出时间限制
class Solution:
    def reversePairs(self, nums: List[int]) -> int:
        # 逆序对数 = 当前序列总对数 - 当前序列通过两两交换排序到全逆序的最少移动次数
        #          = len(nums) * [len(nums)-1] // 2 - swap_times
        
        ### 插入排序
        n = len(nums) 
        swap_times = 0  # 全逆序交换/排列次数
        all_kinds = n * (n-1) // 2  # 总组合数
        
        for i in range(1, n):
            # 当前待比较和交换的元素值及其索引
            cur_val = nums[i]
            cur_idx = i 
            while cur_idx > 0 and cur_val >= nums[cur_idx-1]:  # 顺序对
                nums[cur_idx-1], nums[cur_idx] = nums[cur_idx], nums[cur_idx-1]  # 交换为逆序对
                swap_times += 1  # 交换次数 +1
                cur_idx -= 1  # cur_val 的 idx 更新 (从右往左往回比较)
        print(nums)
        return all_kinds - swap_times

官方说明

# 1188ms - 86.05
class Solution:
    def reversePairs(self, nums: List[int]) -> int:

        # 归并排序 (顺序-从小到大)
        def merge_sort(l, r):
            # 终止条件  左右边界越界
            if l >= r: 
                return 0
            
            # 递归划分
            m = (l + r) // 2  # 数组中点 m
            res = merge_sort(l, m) + merge_sort(m+1, r)  # 左、右子数组逆序数之和
            
            # 合并阶段
            i, j = l, m+1  # i, j 分别指向 左/右子数组 的 首元素/起点
            tmp[l: r+1] = nums[l: r+1]  # 当前左/右子数组索引范围切片 拷贝 - 辅助数组
            
            # 遍历左/右子数组索引范围切片 [l: r+1]
            for k in range(l, r+1):
                # 当 i = m+1 时, 代表左子数组已合并完, 故添加右子数组当前元素 tmp[j], 并令 j += 1
                if i == m+1: 
                    nums[k] = tmp[j]
                    j += 1
                # 否则, 当 j = r+1 时, 代表右子数组已合并完,故添加左子数组当前元素 tmp[i], 并令 i += 1
                # 否则, 当 tmp[i] ≤ tmp[j] (顺序) 时, 添加左子数组当前元素 tmp[i], 并执行 i += 1
                elif j == r+1 or tmp[i] <= tmp[j]:
                    nums[k] = tmp[i]  # 小的先入 (顺序排列)
                    i += 1
                # 否则, 当 tmp[i] > tmp[j] (逆序) 时, 添加右子数组当前元素 tmp[j], 并执行 j += 1, 此时构成 m−i+1 个「逆序对, 统计添加至 res
                else:
                    nums[k] = tmp[j]  # 小的先入 (顺序排列)
                    j += 1
                    res += m-i+1  # 统计逆序对
            # 返回当前逆序对数
            return res 
        
        tmp = [0 for _ in range(len(nums))]  # 辅助数组
        return merge_sort(0, len(nums)-1)

5.3 解答

参考资料:

《剑指 Offer 第二版》

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/o58jfs/

https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/o53yjd/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值