【Datawhale】之LeetCode分类练习(分治)

分治介绍

主要思想
分治算法的主要思想是将原问题递归地分成若干个子问题,直到子问题满足边界条件,停止递归。将子问题逐个击破(一般是同种方法),将已经解决的子问题合并,最后,算法会层层合并得到原问题的答案。
分治算法的步骤
分:递归地将问题分解为各个的子问题(性质相同的、相互独立的子问题);
治:将这些规模更小的子问题逐个击破;
合:将已解决的子问题逐层合并,最终得出原问题的解;
分治法适用的情况

  • 原问题的计算复杂度随着问题的规模的增加而增加。
  • 原问题能够被分解成更小的子问题。
  • 子问题的结构和性质与原问题一样,并且相互独立,子问题之间不包含公共的子子问题。
  • 原问题分解出的子问题的解可以合并为该问题的解。
    相关概念:
    有序度:表示一组数据的有序程度
    逆序度:表示一组数据的无序程度

分治法的设计思想是:将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。

分治策略是:对于一个规模为n的问题,若该问题可以容易地解决(比如说规模n较小)则直接解决,否则将其分解为k个规模较小的子问题,这些子问题互相独立且与原问题形式相同,递归地解这些子问题,然后将各子问题的解合并得到原问题的解。这种算法设计策略叫做分治法。
分治法的复杂性分析
一个分治法将规模为n的问题分成k个规模为n/m的子问题去解。设分解阀值n0=1,且adhoc解规模为1的问题耗费1个单位时间。再设将原问题分解为k个子问题以及用merge将k个子问题的解合并为原问题的解需用f(n)个单位时间。用T(n)表示该分治法解规模为|P|=n的问题所需的计算时间,则有:
T(n)= k T(n/m)+f(n)
通过迭代法求得方程的解:

递归方程及其解只给出n等于m的方幂时T(n)的值,但是如果认为T(n)足够平滑,那么由n等于m的方幂时T(n)的值可以估计T(n)的增长速度。通常假定T(n)是单调上升的,从而当 mi≤n<mi+1时,T(mi)≤T(n)<T(mi+1)。
可使用分治法求解的一些经典问题
(1)二分搜索
(2)大整数乘法
(3)Strassen矩阵乘法
(4)棋盘覆盖
(5)合并排序
(6)快速排序
(7)线性时间选择
(8)最接近点对问题
(9)循环赛日程表
(10)汉诺塔
运用举例
二分查找
分治算法的一个经典应用是二分查找。所谓二分查找,就是在一个排序好的数组中找到目标值,并且输出目标值的坐标。比如在 [0,1,2,3,4,5,6,9,10,12,14,15,16] 中查找 3 返回 3,查找 9 返回 7,查找 8 返回 False。

最简单的查找办法就是按顺序排查,从 0 开始,到 1,到 2,直到找到目标值为止。这样的时间复杂度是 O(n)。在最坏的情况下,我们会将所有的数字检查一遍。

用分治算法我们可以把时间复杂度降到 O(log n)。也就是说在最坏的情况下,我们只需要检查 log2(n) 个数字。通过下面的例子我们看分治算法是怎样提高效率的。我们设目标值为 3。
在这里插入图片描述

  • 在分治算法的第一步中,我们取数组的中间值 6。因为 6 比 3 大,所以我们可以放心地直接排除掉数组的后半部分,并肯定目标值只能在数组的前半部分。
  • 第二步,遵循同样的步骤,对比目标值和中间值,因为 2 比 3 小,所以排除子数组的前半部分。
  • 第三步,对比 4 和 3,排除子数组的后半部分,最终剩余的部分是 [3]。
  • 第四步,因为数组[3] 的长度为 1,所以停止递归。检查数组中的数是否为目标值,如果得到的答案是肯定的,所以直接输出坐标,如果是否定的,就返回 False。因为 3 等于目标值,所以返回 3 的坐标:3。

在以上过程中,每一步我们都将数组一分为二,将原数组变成两个相互独立的子数组,并肯定目标值在其中的一个数组里。通过对比中间值,我们直接排除一个子数组,由此节省时间。

def binarySearch(nums,target):      #传入数组,目标值

def helper(i,j):            #递归方法,i,j为当前数组的起点,终点坐标
    if i==j and nums[i]==target:    
        return i
    if i>=j:                
        return False
    mid = (j-i)//2 + i          #中间值
    if nums[mid] == target:         #如果中间值等于目标值
        return mid
    elif nums[mid]<target:          #如果中间值小于目标值
        return helper(mid+1,j)      #递归,目标值在右侧
    else:
        return helper(i,mid-1)      #目标值在左侧
return helper(0,len(nums)-1)

代码实践

1.Pow(x, n)(50)
实现 pow(x, n) ,即计算 x 的 n 次幂函数。

示例 1:

输入: 2.00000, 10
输出: 1024.00000
示例 2:

输入: 2.10000, 3
输出: 9.26100
说明:

-100.0 < x < 100.0
n 是 32 位有符号整数,其数值范围是 [−231, 231 − 1] 。
通过次数117,838提交次数323,801

解法(来自LeetCode):

class Solution(object):
    def myPow(self, x, n):
        """
        :type x: float
        :type n: int
        :rtype: float
        """
        if n==0:
            return 1  
        if n<0:   				
            return 1/self.myPow(x,-n)
        if n%2:  
            return x*self.myPow(x,n-1)
        return self.myPow(x*x,n/2) 

2.多数元素
给定一个大小为 n 的数组,找到其中的众数。众数是指在数组中出现次数大于 [n/2] 的元素。

你可以假设数组是非空的,并且给定的数组总是存在众数。

class Solution(object):
    def majorityElement2(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        # 【不断切分的终止条件】
        if not nums:
            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    

3.最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大为6。
解题思路:
在这里插入图片描述

class Solution(object):
    def maxSubArray(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        # 【确定不断切分的终止条件】
        n = len(nums)
        if n == 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] # max_l为该数组的最右边的元素
        tmp = 0 # tmp用来记录连续子数组的和
        
        for i in range( len(nums)//2-1 , -1 , -1 ):# 从右到左遍历数组的元素
            tmp += nums[i]
            max_l = max(tmp ,max_l)
            
        # 从左到右计算右边的最大子序和
        max_r = nums[len(nums)//2]
        tmp = 0
        for i in range(len(nums)//2,len(nums)):
            tmp += nums[i]
            max_r = max(tmp,max_r)
            
        # 【对子结果进行合并 得到最终结果】
        # 返回三个中的最大值
        return max(left,right,max_l+ max_r)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值