分治法简单来说就是分而治之。其算法复杂度一般用主定理来求。
所以一般来说,面试: O ( n 2 ) − O ( n l o g n ) − O ( n ) O(n^2)-O(nlogn)-O(n) O(n2)−O(nlogn)−O(n)可以考虑下分治法
def merge(left,right):
if left[-1] < right[0]:
return left + right
n,m = len(a),len(b)
i,j = 0,0
new = []
while i < n and j <m:
if a[i] < b[j]:
new.append(a[i])
i += 1
else :
new.append(b[j])
j += 1
while i < n:
new.append(a[i])
i += 1
while j < m :
new.append(b[j])
j += 1
return new
def merge_sort(nums):
if len(nums) <= 1:
return nums
mid = len(nums)// 2
left = merge_sort(nums[:mid])
right = merge_sort(nums[mid:])
return merge(left,right)
再写一遍,加深印象。
归并的分治是先分到底,再一步步合并。
[a1,a2,a3,b1,b2,b3]——>[a1,b1,a2,b2,a3,b3]
O ( n 2 ) O(n^2) O(n2)做法:
移动数组,b1往前,b2往前。。。
def shuffle(nums):
n = len(nums) // 2
i = n
k = 1
while i < 2 * n - 1:
j = i
while j > k :
nums[j],nums[j-1] = nums[j-1],nums[j]
j -= 1
k += 2
i += 1
return nums
注意下起始点
分治做法:
def switch(num1,num2,l):
n = len(num1)
i = n - l
for j in range(0,l):
num1[i],num2[j] = num2[j],num1[i]
i += 1
return num1,num2
def shuffle1(nums):
mid = len(nums) // 2
res = len(nums) % 2
mid = mid + res
if len(nums) <= 2:
return nums
else:
l = mid // 2
nums_left,nums_right = switch(nums[:mid],nums[mid:],l)
num_s_left = shuffle1(nums_left)
num_s_right = shuffle1(nums_right)
return num_s_left + num_s_right
与归并不同,这边是分治分治分治
a1a2a3 * b1b2b3b4:
a1a2 | a3
b1b2 | b3b4
同样是分治法,与直接遍历的差别在于中间项少算一次
def quick_plus(x,y):
if len(str(x)) == 1 or len(str(y)) == 1:
return x*y
else:
n = max(len(str(x)) , len(str(y)))
pow = n // 2
a = x // (10 ** pow )
b = x % (10 ** pow )
c = y // (10 ** pow )
d = y % (10 ** pow )
ac = quick_plus(a,c)
bd = quick_plus(b,d)
mid_term = quick_plus(a+b,c+d) - ac - bd
ans = ac *(10 ** (2*pow ) ) + mid_term * (10 ** pow ) + bd
return ans
分而治之,并不一定要均等分。比如快速排序,其实就是将找到的pivot位置后切分。
找第k小的元素也可以用分治法。
def findKthSmallest(self, nums: List[int], k: int) -> int:
if nums:
pos = self.partition(nums, 0, len(nums)-1) #最右的元素放到了正确的位置,并且返回下标配
if k > pos+1:
return self.findKthSmallest(nums[pos+1:], k-pos-1)
elif k < pos+1:
return self.findKthSmallest(nums[:pos], k)
else:
return nums[pos]
# 将最右边的元素放置到正确的位置,
def partition(self,nums, l, r):# 看看最右的元素的坐标
low = l
while l < r:
if nums[l] < nums[r]: #当 大小符合,low与l共同移动,并与原来的交换。当不符合时,low停住
nums[l], nums[low] = nums[low], nums[l]
low += 1
l += 1 #l的目的是为了遍历数组
nums[low], nums[r] = nums[r], nums[low]
return low
partition当然也可以用列表解析来写
def partition(self,nums, l, r):# 看看最右的元素的坐标
left = [x for x in nums if x < nums[r]]
right = [x for x in nums if x > nums[r]]
eq = [x for x in nums if x == nums[r]]
low = len(left)
nums = left + eq + right
return low,nums
这样会额外占用空间,而且多遍历了数组(遍历3遍),会更慢。
三种做法:
分治法——三部分的合并
def maxSubArray(self, nums: List[int]) -> int:
if len(nums) == 1:
return nums[0]
mid = len(nums)// 2
left = self.maxSubArray(nums[:mid])
right = self.maxSubArray(nums[mid:])
return max(left,right,self.getmid(nums,mid))
def getmid(self,nums,mid):
l,r = nums[:mid],nums[mid:]
i,j = len(l) - 1 ,0
max_l = -sys.maxsize
max_r = -sys.maxsize
sum_l = 0
while i >= 0:
sum_l += l[i]
max_l = max(max_l,sum_l)
i -= 1
sum_r = 0
while j < len(r):
sum_r += r[j]
max_r = max(max_r,sum_r)
j += 1
return max_l +max_r
在归并的时候统计逆序对的数量即可。
def reversePairs(self, nums: List[int]) -> int:
return self.reversePairs1(nums)[1]
def reversePairs1(self, nums: List[int]) -> int:
if len(nums) < 2 :
return nums,0
mid = len(nums) // 2
left,cnt_l = self.reversePairs1(nums[:mid]) #不仅返回左边有序,返回左边的逆序对数目
right,cnt_r = self.reversePairs1(nums[mid:]) #返回右边的逆序对数目
merged,cnt = self.merge(left,right) #最优两边合并时新增的逆序对数目
cnt += (cnt_l + cnt_r) #汇总求和
return merged,cnt
def merge(self,left,right):
m,n = len(left),len(right)
cnt = 0
i,j = 0,0
res = []
while i < m and j < n:
if left[i] <= right[j]:
res.append(left[i])
i += 1
else: #当左i 大于 右j
res.append(right[j])
cnt += m - i #说明左边i以后的元素都大于j,所以对j而言,有m-i的逆序对。
j += 1
if i < m:
res += left[i:]
if j < n :
res += right[j:]
return res,cnt
最终返回的是一个元组,记录有序的数组以及逆序对的数量
给定一个整数数组 nums,按要求返回一个新数组 counts。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。
输入: [5,2,6,1]
输出: [2,1,1,0]
与计算逆序对类似,不过需要记录的是每个元素的逆序对个数。这个counts数组的和就是逆序对的数目。
def countSmaller(self, nums: List[int]) -> List[int]:
def merge_sort(num):
if len(num) <= 1:
return num
mid = len(num) // 2
left = merge_sort(num[:mid])
right = merge_sort(num[mid:])
return merge(left,right)
def merge(left,right):
i,j = 0,0
m,n = len(left),len(right)
new = []
while i < m and j < n:
if left[i][1] <= right[j][1]:
new.append(left[i])
ans[left[i][0]] += j #最关键的是这一行
i += 1
else :
new.append(right[j])
j += 1
while i < m:
new.append(left[i])
ans[left[i][0]] += j
i += 1
while j < n:
new.append(right[j])
j += 1
return new
enums = list(enumerate(nums))
ans = [0]*len(nums)
merge_sort(enums)
return ans
与逆序对写法不一样的地方