欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 房产 > 家装 > 【算法】(Python)分治算法

【算法】(Python)分治算法

2025/10/21 1:02:02 来源:https://blog.csdn.net/yannan20190313/article/details/143591292  浏览:    关键词:【算法】(Python)分治算法

分治算法:

  • 分而治之。
  • 将一个问题分成若干个子问题(甚至更小的子子问题),子问题相互独立、与原问题性质相同。
  • 子问题的解可以合并得到原问题的解。
  • 应用:二分查找、归并排序、快速排序等。
  • 步骤:① 将问题分成多个同类子问题,② 求解子问题,③ 将子问题的解逐层合并获得原问题的解。


1、(难度:简单)【力扣】LCR 161. 连续天数的最高销售额

解题思路:将数组从中间拆分左右两部分,直到拆分成一个一个的元素,再逐层合并左右两部分,获取四个数据的最大和。最终返回中间部分最大和。

四个数据的最大和:

  • 左侧最大和:左侧第一个元素开始往右的最大和。涉及2个情况:左子区间的左侧最大和、左子区间的总和+右子区间的左侧最大和,取最大值。
  • 右侧最大和:右侧第一个元素开始往左的最大和。涉及2个情况:右子区间的右侧最大和、右子区间的总和+左子区间的右侧最大和,取最大值。
  • 中间最大和:中间位置的最大和。涉及3个情况:左子区间的中间最大和、右子区间的中间最大和、左子区间的右侧最大和+右子区间的左侧最大和,取最大值。
  • 区间总和:整个区间的和。包括左子区间的总和、右子区间的总和。

时间复杂度:O(n)。空间复杂度:O(logn)。

class Solution:def maxSales(self, sales: List[int]) -> int:def func(left:int, right:int):# 终止条件:拆分到只剩一个元素,返回if left == right:# 返回左侧最大和、右侧最大和、中间最大和、区间总和(均为该元素)return [sales[left]] * 4# 从中间拆分成左右子区间mid = (left + right) >> 1l_lsum, l_rsum, l_msum, l_isum = func(left, mid)r_lsum, r_rsum, r_msum, r_isum = func(mid + 1, right)# 合并左右子区间# 从左侧开始的最大和:# max(左子区间的左侧最大和, 左子区间的区间总和+右子区间的左侧最大和)lsum = max(l_lsum, l_isum + r_lsum)# 从右侧开始的最大和:# max(右子区间的右侧最大和, 右子区间的区间总和+左子区间的右侧最大和)rsum = max(r_rsum, r_isum + l_rsum)# 中间的最大和:# max(max(左子区间的中间最大和,右子区间的中间最大和), 左子区间的右侧最大和+右子区间的左侧最大和)msum = max(max(l_msum, r_msum), l_rsum + r_lsum)# 区间总和:# 左子区间的区间总和+右子区间的区间总和isum = l_isum + r_isum# 返回左侧最大和、右侧最大和、中间最大和、区间总和return [lsum, rsum, msum, isum]# 返回最终中间最大和return func(0, len(sales) - 1)[2]

使用面向对象(创建类),类中包括四个数据。

# 类foursum
class foursum:def __init__(self, lsum, rsum, msum, isum):self.lsum = lsum         # 从左侧开始的最大和self.rsum = rsum         # 从右侧开始的最大和self.msum = msum         # 中间的最大和self.isum = isum         # 区间总和class Solution:def maxSales(self, sales: List[int]) -> int:# 合并左右子区间def mergemax(l:foursum, r:foursum):lsum = max(l.lsum, l.isum + r.lsum)rsum = max(r.rsum, r.isum + l.rsum)msum = max(max(l.msum, r.msum), l.rsum + r.lsum)isum = l.isum + r.isum# 返回:类实例(左侧最大和、右侧最大和、中间最大和、区间总和)return foursum(lsum, rsum, msum, isum)# 获取最大和def get(left:int, right:int):# 终止条件:拆分到只剩一个元素,返回if left == right:return foursum(sales[left], sales[left], sales[left], sales[left])# 从中间拆分成左右子区间mid = (left + right) >> 1lsub = get(left, mid)rsub = get(mid + 1, right)# 返回合并左右子区间后的结果return mergemax(lsub, rsub)# 返回最终中间最大和return get(0, (len(sales) - 1)).msum

注:本题其他解决方法:动态规划。本文忽略。


2、(难度:中等)【力扣】109. 有序链表转换二叉搜索树

平衡二叉搜索树:

各节点的左子树都比该节点数值小,右子树都比该节点数值大。

各节点的左子树和右子树的高度差最多为1。 

 解题思路:从链表中间位置,逐次拆分成左右两部分(相当于左右子树),再逐层构建平衡二叉搜索树。

(本处中位数为:总个数为奇数,中位数为中间的数。总个数为偶数,中位数为中间2个数中的后面那个数)

本题有两种方法:

  • ① 使用快慢指针找到中位数,逐次拆分递归构建左右子树,
  • ② 获取链表总个数,通过计算找到中位数,使用中序遍历方式递归构建平衡二叉搜索树。

方法 ①:

注意:快慢指针起始都指向链表左端点。慢指针走1步,快指针走2步,快指针走完,慢指针到达中位数的位置。

链表总个数有奇偶,慢指针到达中位数时,快指针指向位置有两种情况,

  1. 链表总个数为奇数时,快指针到右端点的最后那个节点(fast.next=None),
  2. 链表总个数为偶数时,快指针到右端点后的空节点(fast=None)。

时间复杂度:O(nlogn)。空间复杂度:O(logn)。

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:def sortedListToBST(self, head: Optional[ListNode]) -> Optional[TreeNode]:# 查找中位数def getmid(left:ListNode, right:ListNode):# 快慢指针,初始都指向链表左端点slow, fast = left, left# 快指针走2步,慢指针走1步,快指针到右端点(总个数分奇偶),则慢指针指向的是中位数while fast != right and fast.next != right:slow = slow.nextfast = fast.next.next# 返回慢指针return slow# 转换为平衡二叉搜索树def buildtree(left:ListNode, right:ListNode):# 终止条件if left == right: return# 获取中位数mid = getmid(left, right)# 根节点为中位数的内容root = TreeNode(mid.val)# 左子节点root.left = buildtree(left, mid)# 右子节点root.right = buildtree(mid.next, right)# 返回节点return root# 返回最终的平衡二叉搜索树return buildtree(head, None)

方法 ②:

注意:左右边界是否包含。本方法 左右边界均包含在范围内。左边界left起始为0,右边界right起始为链表长度-1,中位数mid为(左边界+右边界+1)除以2取整。

时间复杂度:O(n)。空间复杂度:O(logn)。

知识点:a >> 1:数值a二进制左移1位,即a//2,a除以2取整。

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:def sortedListToBST(self, head: Optional[ListNode]) -> Optional[TreeNode]:# 获取链表长度,(用于获取中位数)def getlen(head:ListNode):n = 0while head:head = head.nextn += 1return n# 转换为平衡二叉搜索树def buildtree(left:int, right:int):# 终止条件if left > right: return# 获取中位数mid = (left + right + 1) >> 1# 使用中序遍历构建树# 左子节点node = TreeNode()node.left = buildtree(left, mid - 1)# 根节点nonlocal headnode.val = head.valhead = head.next# 右子节点node.right = buildtree(mid + 1, right)# 返回节点return node# 获取链表长度n = getlen(head)# 返回最终的平衡二叉搜索树return buildtree(0, n - 1)

3、(难度:困难)【力扣】315. 计算右侧小于当前元素的个数

解题思路:从数组中间位置,逐步拆分成左右两部分,直到一个一个的元素,再逐层将左右两部分按从小到大顺序合并排列。合并结果存放到临时列表tmp中,各元素对应的下标存放到临时索引列表tmpindex中,统计逆序对(右侧元素比左侧元素小)的个数。

注意:

  1. 记录合并排序结果和元素对应的下标,可用元组形式,即(元素,下标)。本文使用两个列表tmp和tmpindex分别记录。
  2. 本文使用的方法中,每次合并排序后,都要用排序后的结果tmp和对应的下标tmpindex更新原列表nums和对应的索引列表index,便于下一轮合并排序。

时间复杂度:O(nlogn)。空间复杂度:O(n)。

知识点: 逆序对:数学术语。数组A,正整数i,j,使得 1\leq i < j \leqn 且 A(i) > A(j),则<A(i), A(j)>为逆序对。

class Solution:def countSmaller(self, nums: List[int]) -> List[int]:# 归并排序def mergesort(left:int, right:int):# 终止条件if left >= right: return# 获取中间数mid = (left + right) >> 1# 拆分为左右两部分mergesort(left, mid)mergesort(mid + 1, right)# 合并左右两部分merge(left, mid, right)# # 合并左右两部分def merge(left:int, mid:int, right:int):# 两个指针,初始分别指向左部分和右部分第一个元素i, j = left, mid + 1# 指向tmp列表中元素的指针k = left# 左右两部分都有元素,数值小的记录到tmp列表,对应下标记录到tmpindex列表while i <= mid and j <= right:# 若右部分的元素小if nums[i] > nums[j]: tmp[k] = nums[j]         # 较小的元素 存入tmptmpindex[k] = index[j]   # 较小的元素的下标 存入tmpindexk += 1j += 1# 若左部分的元素小else: tmp[k] = nums[i]         # 较小的元素 存入tmptmpindex[k] = index[i]   # 较小的元素的下标 存入tmpindex# 记录逆序对,右部分j之前的元素都小于左部分i的元素counts[index[i]] += (j- mid - 1)k += 1                    i += 1# 右部分遍历完。左部分仍有元素,添加到tmp列表while i <= mid:tmp[k] = nums[i]         # 较小的元素 存入tmptmpindex[k] = index[i]   # 较小的元素的下标 存入tmpindex# 记录逆序对,右部分j之前的元素都小于左部分i的元素counts[index[i]] += (j- mid - 1)k += 1                    i += 1# 左部分遍历完。右部分仍有元素,添加到tmp列表while j <= right:tmp[k] = nums[j]         # 较小的元素 存入tmptmpindex[k] = index[j]   # 较小的元素的下标 存入tmpindexk += 1j += 1# 排序后的tmp列表中数据更新nums,tmpindex列表中数据更新indexfor x in range(left, right + 1):# 合并排序后的各元素下标,用于下一轮合并排序index[x] = tmpindex[x]nums[x] = tmp[x]n = len(nums)# 记录各元素右侧小于它的个数,初始都为0counts = [0] * n# 索引列表,记录各元素在初始nums列表中对应的下标位置index = [i for i in range(len(nums))]# 左右部分合并排序时,记录从小到大排列结果tmp = [0] * n# 左右两部分合并排序时,记录tmp中各元素对应的下标位置tmpindex = [0] * n# 执行归并排序mergesort(0, n - 1) return counts

本题还有其他的解题办法:离散化树状数组,本文忽略。原始办法:两重循环进行枚举。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词