Datawhale编程实践Task01-分治

本文所有题目来自于leetcode官网https://leetcode-cn.com/

分治法简介

在计算机科学中,分治法是基于多项分支递归的一种很重要的算法范式。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。

其实分治法最重要的就是递归,一般而言,递归呢,就是要“套娃”,将主要问题拆分成很小的问题解决,然后层层递归到主问题中。

接下来将通过以下三个leetcode题目来介绍分治法的应用

leetcode50. pow(x,n)幂函数

题目:实现 pow(x, n) ,即计算 x 的 n 次幂函数。

示例 1:

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

输入: 2.10000, 3
输出: 9.26100
示例 3:

输入: 2.00000, -2
输出: 0.25000
解释: 2-2 = 1/22 = 1/4 = 0.25
说明:

  • -100.0 < x < 100.0
  • n 是 32 位有符号整数,其数值范围是 [−231, 231 − 1] 。
class Solution:
    def myPow(self, x: float, n: int) -> float:
        """
            x: base of the Exponentiation
            n: exponent of the Exponentiation
        """
        if n == 0:
            return 1
        if n < 0:
            x = 1 / x
            n = abs(n)
        res = 1
        while n >= 1:
            if n & 1 == 1:
                res = res * x
            x = x * x
            n >>= 1
        return res

这里用了快速幂的算法,其思路总体概括就是
a n = a 2 ∗ ( n / 2 ) = ( a 2 ) n / 2 , n 为 偶 数 a n = a ∗ a n − 1 = a ∗ a ( n − 1 ) / 2 , n 为 奇 数 a^n = a^{2*(n/2)} = (a^2)^{n/2}, n为偶数\\ a^n = a*a^{n-1} = a*a^{(n-1)/2}, n为奇数 an=a2(n/2)=(a2)n/2,nan=aan1=aa(n1)/2,n
这样一直递归到各个指数的幂都是0,然后反递归即可得到。上面是将递归转成迭代,下面是递归的方法

class Solution:
    def myPow(self, x: float, n: int) -> float:
        """
            x: base of the Exponentiation
            n: exponent of the Exponentiation
        """
        if n < 0:
            x = 1 / x
            n = abs(n)

        def power(base, exp):
            if exp == 0:
                return 1
            if exp & 1 == 1:
                return base * power(base, exp - 1)
            else:
                return power(base * base, exp // 2)

        return power(x, n)

leetcode53. 最大子序和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例:

输入: [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
进阶:

如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。

class Solution:
    def maxSubArray(self, nums: [int]) -> int:
        s =[]
        sumOfnums = 0
        if len(nums)<2:
            return sum(nums)
        #将每一个从0开始到i个元素求和,结果放入列表中
        for i in range(len(nums)):
            sumOfnums+=nums[i]
            s.append(sumOfnums)
        return max(self.maxdeviation(s),max(nums))
    def maxdeviation(self,nums:[int])->int:
        if len(nums)<2:
            return sum(nums)
        max_index = nums.index(max(nums))
        min_index = nums.index(min(nums))
        if max_index>min_index:
            return max(nums[max_index]-nums[min_index],max(nums))
        else:
            #分别递归地求最大值左边的最大差值和右边的最大差值
            return max(self.maxdeviation(nums[:max_index+1]),
                       self.maxdeviation(nums[max_index+1:]))

看到题目是个跟连续子序列相关的题目,首先就想到了动态规划,但可能好长时间不刷题了,没有想到求以第i个元素结尾的最大子序列和。我上面得思路是先求出前n个数的和(这里记为 s n s_n sn)并放入列表中,在之后求出这些数的最大偏差记为最大值。

即:

令nums = [ a 1 , a 2 , . . . , a n ] [a_1,a_2,...,a_n] [a1,a2,...,an]
s 1 = a 1 s 2 = a 1 + a 2 s 3 = a 1 + a 2 + a 3 . . . s n = a 1 + a 2 + . . . + a n s_1=a_1\\ s_2 = a_1+a_2\\ s_3 = a_1+a_2+a_3\\ ...\\ s_n = a_1+a_2+...+a_n\\ s1=a1s2=a1+a2s3=a1+a2+a3...sn=a1+a2+...+an
所以任意一个连续子序列( a i 到 a j , i < j a_i到a_j,iaiaj,i<j)的和都可以用 s j − s i s_j-s_i sjsi来表示。那么我们接下来的目标就是求得这样差值的最大值

然后我其实是进行了分类讨论,如果s中最大值在最小值的右边,那么很简单输出最大值-最小值。而如果在左边,我们就用分治思想进行递归,分别计算最大值左边的最大差值和右边的最大差值并取最大。但是这里忽略了最大值右边那个数单个数,所以我直接暴力的将最后得到的最大和单个元素最大又进行比较取最大。

时间复杂度是O(n),空间复杂度O(n)

class Solution:
    def maxSubArray(self, nums: [int]) -> int:
        before = nums[0]
        max_t = before
        for i in range(1,len(nums)):
            before = max(before+nums[i],nums[i])
            max_t = max(max_t,before)
        return max_t

上面就是动态规划的思想,即记录并比较以数组中每一个元素为结尾的子序和的最大值

leetcode169. 多数元素

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

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

示例 1:

输入: [3,2,3]
输出: 3
示例 2:

输入: [2,2,1,1,1,2,2]
输出: 2

class Solution:
    def majorityElement(self, nums: [int]) -> int:
        dic = {}
        for i in nums:
            if i in dic.keys():
                dic[i]+=1
            else:
                dic[i] = 1
        max_value = 0
        for key,value in dic.items():
            if max_value<value:
                max_value = value
                max_key = key
        return max_key

这边最容易的想法就是用遍历一下数组,然后用哈希表存下每个数出现的次数,然后输出出现次数最多的那个数。时间复杂度O(n),空间复杂度O(n)。

但如果是用到分治的方法,我们可以考虑既然要找出那个出现次数超过n/2的元素(也就是众数),那么我们每次分割之后左右两边的众数中必有一个是整个的众数,那么在递归过程中只要每次都判断一下归并后两边的众数哪一个是归并后集合的众数。

class Solution:
    def majorityElement(self, nums: [int]) -> int:
        return self.maj_Ele_div(nums)

    def maj_Ele_div(self, nums: [int]):
        if len(nums) == 1:
            return nums[0]
        m = len(nums) // 2
        maj_left = self.maj_Ele_div(nums[:m])
        maj_right = self.maj_Ele_div(nums[m:])
        count_left = sum([1 for item in nums if item == maj_left])
        count_right = sum([1 for item in nums if item == maj_right])
        return maj_left if count_left >= count_right else maj_right

可以明显这个方法时间复杂度要稍微高一些,为O(nlogn)。在leetcode官方答案里还有一个Boyer-Moore 投票算法

Boyer-Moore 算法的本质和方法四中的分治十分类似。我们首先给出 Boyer-Moore 算法的详细步骤:

  • 我们维护一个候选众数 candidate 和它出现的次数 count。初始时 candidate 可以为任意值,count 为 0;

  • 我们遍历数组 nums 中的所有元素,对于每个元素 x,在判断 x 之前,如果 count 的值为 0,我们先将 x 的值赋予 candidate,随后我们判断 x:

  • 如果 x 与 candidate 相等,那么计数器 count 的值增加 1;

  • 如果 x 与 candidate 不等,那么计数器 count 的值减少 1。

  • 在遍历完成后,candidate 即为整个数组的众数。

class Solution:
    def majorityElement(self, nums: [int]) -> int:
        count = 0
        candidate = None
        for i in nums:
            if count == 0:
                candidate = i
            if candidate == i:
                count += 1
            else:
                count -= 1
        return candidate

参考文献:

  1. 维基百科,链接:https://zh.wikipedia.org/wiki/%E5%88%86%E6%B2%BB%E6%B3%95

  2. 力扣网,链接:https://leetcode-cn.com/

你可能感兴趣的:(leetcode,leetcode)