分治
中心思想
分治算法的中心思想是将原问题递归地分成若干个子问题,直到子问题满足边界条件,停止递归。将子问题逐个击破,将已经解决地子问题合并,最后,算法会层层合并得到答案。
分治步骤及递归三定律
分治步骤
分:递归地将问题分解为各个的子问题(性质相同的、相互独立的子问题);
治:将这些规模更小的子问题逐个击破;
合:将已解决的子问题逐层合并,最终得出原问题的解;
递归三定律
具体地,可以以下步骤进行
1.基本结束条件:递归算法必须有一个基本结束条件,最小规模的问题可以直接解决;
2.减小问题规模:递归算法必须能改变状态想基本结束条件演进;
3.调用自身:递归算法必须能够调用自身,解决减小规模的相同问题,同时需要对相同问题归并。
以本周分治的三个题目为例:
第一题: Leetcode 169 多数元素
给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入: [3,2,3]
输出: 3
示例 2:
输入: [2,2,1,1,1,2,2]
输出: 2
牢记分治策略以及递归三定律,具体应在题目中:
1.基本结束条件:如果数组中只有一个元素,多数元素肯定是该元素;
2.减小问题规模:二分策略,将目标缩小;
3.调用自身:原数组被分成了多个不同的数组,当子问题无法进行拆分的时候,开始归并问题,具体过程如下:
class Solution:
def majorityElement(self, nums: List[int]) -> int:
#当列表为空时
if nums == None:
return None
#基本结束条件:数组里只有一个元素
if len(nums) == 1:
return nums[0]
#缩小规模:将原来数组二分为两个数组
#分别调用自身,将一个父问题分为两个子问题
left = self.majorityElement(nums[:len(nums)//2])
right = self.majorityElement(nums[len(nums)//2:])
#对划分的两个子问题归并,归并结果就是原来父问题的结果
if left == right:
return left
if nums.count(left) > nums.count(right):
return left
else:
return right
重点在于对归并理解,如果对归并的过程比较模糊,可以手动写一下代码的执行过程,这样有利于理解整个分治的过程,从而加深对归并过程的理解;
时间复杂度:O(nlogn);
空间复杂度:O(logn)
另外,本题其实还有其他的解法:如哈希表(时间复杂度和空间复杂度均为O(N))等,效率高于分治这里不做讨论;
第二题: Leetcode 53.最大子序和.
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
进阶:
如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
#基本结束条件:当数组长度为1
if len(nums) == 1:
return nums[0]
#减小规模:将元素组二分为两个数组
#调用自身:对两个子问题求子序列
left = self.maxSubArray(nums[:len(nums)//2])
right = self.maxSubArray(nums[len(nums)//2:])
#对子问题进行归并
max_l = nums[len(nums)//2 - 1]
tmp_l = 0
for i in range(len(nums)//2 - 1, -1, -1):
tmp_l = tmp_l + nums[i]
max_l = max(max_l, tmp_l)
max_r = nums[len(nums)//2]
tmp_r = 0
for i in range(len(nums)//2, len(nums)):
tmp_r = tmp_r + nums[i]
max_r = max(max_r, tmp_r)
return max(left, right, max_l + max_r)
时间复杂度:渐进时间复杂度为 O(n)。
空间复杂度:递归会使用 O(logn) 的栈空间,故渐进空间复杂度为 O(logn)。
和这个题目类似的