leetcode之分治算法

leetcode分治算法

  • Pow(x, n)
    • 优化 one time
      • 从正负数的角度考虑分冶
    • 优化 two times
      • 从奇偶数的角度考虑分冶
  • 最大子序和
  • 多数元素

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] 。

优化 one time

从正负数的角度考虑分冶

class Solution(object):
    def myPow(self, x, n):
        """
        :type x: float
        :type n: int
        :rtype: float
        """
        #如何判断一个数为正数还是负数,大于还是小于0
        #如果n是正数,则n个x相乘
        #如果n是负数,则n个1/x相乘
        i=0
        result=1
        if  n  >0:
            for i in n:
                result*=x
        else if n<0:
            for i in -n:
                result*=1/x
        else:
            result=1
        
        return result

报错 Memory error
leetcode之分治算法_第1张图片
解决方案

class Solution(object):
    def myPow(self, x, n):
        """
        :type x: float
        :type n: int
        :rtype: float
        """
        i=0
        result=1
        if n>0:
            result=x**n
            # for i in range(n):
            #     result*=x
        elif n<0:
            result=(1/x)**(-n)
            # for i in range(-n):
            #     result*=1/x
        else:
            result=1
        
        return result

在解决问题的同时,看题解的过程中,发现了快速幂算法

	3^10=3*3*3*3*3*3*3*3*3*3
	
	//尽量想办法把指数变小来,这里的指数为10
	
	3^10=(3*3)*(3*3)*(3*3)*(3*3)*(3*3)
	
	3^10=(3*3)^5
	
	3^10=9^5
	
	//此时指数由10缩减一半变成了5,而底数变成了原来的平方,求3^10原本需要执行10次循环操作,求9^5却只需要执行5次循环操作,但是3^10却等于9^5,我们用一次(底数做平方操作)的操作减少了原本一半的循环量,特别是在幂特别大的时候效果非常好,例如2^10000=4^5000,底数只是做了一个小小的平方操作,而指数就从10000变成了5000,减少了5000次的循环操作。
	
	//现在我们的问题是如何把指数5变成原来的一半,5是一个奇数,5的一半是2.5,但是我们知道,指数不能为小数,因此我们不能这么简单粗暴的直接执行5/2,然而,这里还有另一种方法能表示9^5
	
	9^5=(9^4)*(9^1)
	
	//此时我们抽出了一个底数的一次方,这里即为9^1,这个9^1我们先单独移出来,剩下的9^4又能够在执行“缩指数”操作了,把指数缩小一半,底数执行平方操作
	
	9^5=(81^2)*(9^1)
	
	//把指数缩小一半,底数执行平方操作
	
	9^5=(6561^1)*(9^1)
	
	//此时,我们发现指数又变成了一个奇数1,按照上面对指数为奇数的操作方法,应该抽出了一个底数的一次方,这里即为6561^1,这个6561^1我们先单独移出来,但是此时指数却变成了0,也就意味着我们无法再进行“缩指数”操作了。
	
	9^5=(6561^0)*(9^1)*(6561^1)=1*(9^1)*(6561^1)=(9^1)*(6561^1)=9*6561=59049
	
	我们能够发现,最后的结果是9*6561,而9是怎么产生的?是不是当指数为奇数5时,此时底数为9。那6561又是怎么产生的呢?是不是当指数为奇数1时,此时的底数为6561。所以我们能发现一个规律:最后求出的幂结果实际上就是在变化过程中所有当指数为奇数时底数的乘积。

优化 two times

从奇偶数的角度考虑分冶

leetcode之分治算法_第2张图片

class Solution(object):
    def myPow(self, x, n):
        """
        :type x: float
        :type n: int
        :rtype: float
        """
        if x == 0.0: return 0.0
        res = 1
        if n < 0: x, n = 1 / x, -n
        while n:
            if n & 1: res *= x
            x *= x
            n >>= 1
        return res

最大子序和

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

示例:

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

leetcode之分治算法_第3张图片
leetcode之分治算法_第4张图片

class Solution:
    def maxSubArray(self, nums) :
        n = len(nums)
        #递归终止条件
        if n == 1:
            return nums[0]
        else:
            #递归计算左半边最大子序和
            max_left = self.maxSubArray(nums[0:len(nums) // 2])
            #递归计算右半边最大子序和
            max_right = self.maxSubArray(nums[len(nums) // 2:len(nums)])
        
        #计算中间的最大子序和,从右到左计算左边的最大子序和,从左到右计算右边的最大子序和,再相加
        max_l = nums[len(nums) // 2 - 1]
        tmp = 0
        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(max_right,max_left,max_l+max_r)

多数元素

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

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

 

示例 1:

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

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

思路

如果数 a 是数组 nums 的众数,如果我们将 nums 分成两部分,那么 a 必定是至少一部分的众数。

我们可以使用反证法来证明这个结论。假设 a 既不是左半部分的众数,也不是右半部分的众数,那么 a 出现的次数少于 l / 2 + r / 2 次,其中 l 和 r 分别是左半部分和右半部分的长度。由于 l / 2 + r / 2 <= (l + r) / 2,说明 a 也不是数组 nums 的众数,因此出现了矛盾。所以这个结论是正确的。

这样以来,我们就可以使用分治法解决这个问题:将数组分成左右两部分,分别求出左半部分的众数 a1 以及右半部分的众数 a2,随后在 a1 和 a2 中选出正确的众数。

算法

我们使用经典的分治算法递归求解,直到所有的子问题都是长度为 1 的数组。长度为 1 的子数组中唯一的数显然是众数,直接返回即可。如果回溯后某区间的长度大于 1,我们必须将左右子区间的值合并。如果它们的众数相同,那么显然这一段区间的众数是它们相同的值。否则,我们需要比较两个众数在整个区间内出现的次数来决定该区间的众数。
class Solution:
    def majorityElement(self, nums, lo=0, hi=None):
        def majority_element_rec(lo, hi):
            # base case; the only element in an array of size 1 is the majority
            # element.
            if lo == hi:
                return nums[lo]

            # recurse on left and right halves of this slice.
            mid = (hi-lo)//2 + lo
            left = majority_element_rec(lo, mid)
            right = majority_element_rec(mid+1, hi)

            # if the two halves agree on the majority element, return it.
            if left == right:
                return left

            # otherwise, count each element and return the "winner".
            left_count = sum(1 for i in range(lo, hi+1) if nums[i] == left)
            right_count = sum(1 for i in range(lo, hi+1) if nums[i] == right)

            return left if left_count > right_count else right

        return majority_element_rec(0, len(nums)-1)



你可能感兴趣的:(算法)