Leetcode编程训练1 分治方法

python调包很方便,数据结构学着总是半途而废,所以一直没有学完。DataWhale的Leetcode练习,对我来说很难,每个题我基本上只会常用库API函数实现,但想着有伴可以一起做这件事,有专业的引导,有拼比,有相互扶持,好像艰难的事情变得不那么难了。【话说的太早,笑到最后才是成功,争取提前1天完成学习内容,这样学习任务重的日子也可以来得及,加油!】

学习目标

了解分治算法

分治算法(divide and conquer, D&C)是一种著名的递归式问题解决方法,一个重要的D&C算法就是快速排序。
使用D&C解决问题的过程包括两个步骤:

  1. 找出基线条件,这种条件尽可能简单
  2. 不断将问题分解,直到符合基线条件。

50 Pow(x, n)

问题

x n 的 结 果 。 x^n的结果。 xn

边界情况:
  • n = 0→任何值x的0次幂结果为1
  • n < 0→转换成1/x的-n次幂的问题
三个思路

【leetcode的讲解太妙,我只能从推导来证明我貌似了解它们的思路了】

  • 思路1: x n = x ∗ x ∗ x ∗ . . . ∗ x x^n = x*x*x*...*x xn=xxx...x
  • 思路2: x n = ( x n / 2 ) 2 = ( ( x n / 4 ) 2 ) 2 = . . . . = ( ( ( ( x 1 ) 2 ) 2 ) . . . ) 2 x^n = (x^{n/2})^2=((x^{n/4})^2)^2=....=((((x^1)^2)^2)...)^2 xn=(xn/2)2=((xn/4)2)2=....=((((x1)2)2)...)2
  • 思路3【原理是位运算】:
    n = k 0 2 0 + k 1 2 1 + k 2 2 2 + . . . + k m 2 m , k i ∈ 0 , 1 n = k_02^0 + k_12^1 + k_22^2+ ...+k_m2^m,k_i∈{0,1} n=k020+k121+k222+...+km2mki0,1
    x n = x ( k 0 2 0 + k 1 2 1 + k 2 2 2 + . . . + k m 2 m ) = x ( k 0 2 0 + k 1 2 0 ∗ 2 + k 2 2 0 ∗ 2 ∗ 2 + . . . + k m 2 0 ∗ 2 ∗ 2... ∗ 2 ) = x k 0 2 0 ∗ x k 1 2 0 ∗ 2 ∗ x k 2 2 0 ∗ 2 ∗ 2 ∗ . . . ∗ x k m 2 0 ∗ 2 ∗ . . . ∗ 2 = x k 0 2 0 ∗ ( x k 1 2 0 ) 2 ∗ ( ( x k 2 2 0 ) 2 ) 2 ∗ . . . ∗ ( ( ( x k m 2 0 ) 2 ) 2 . . . ) 2 x^n = x^{(k_02^0 + k_12^1 + k_22^2+ ...+k_m2^m)}=x^{(k_02^0 + k_12^0*2 + k_22^0*2*2+...+k_m2^0*2*2...*2)}=x^{k_02^0}*x^{k_12^0*2}*x^{k_22^0*2*2}*...*x^{k_m2^0*2*...*2}=x^{k_02^0}*(x^{k_12^0})^2*((x^{k_22^0})^2)^2*...*(((x^{k_m2^0})^2)^2...)^2 xn=x(k020+k121+k222+...+km2m)=x(k020+k1202+k22022+...+km2022...2)=xk020xk1202xk22022...xkm202...2=xk020(xk120)2((xk220)2)2...(((xkm20)2)2...)2
方法代码
'''方法1 迭代处理'''
class Solution(object):
	# 由于存在迭代,算法复杂度为O(n)
	def myPow(self, x, n):
		ans = 1
		if n < 0:
			x = 1 / x
			n = -n
		elif n == 0:
			return ans 
			
		for _ in range(n):
			ans *= x
		return ans
'''方法2 分治递归'''
class Solution(object):
	# 时间复杂度:O(log⁡n),即为递归的层数。
	# 空间复杂度:O(log⁡n),即为递归的层数。这是由于递归的函数调用会使用栈空间。
	def myPow(self, x, n):
		if n < 0:
			x = 1 / x
			n = -n
		elif n == 0:
			return 1
		p = n // 2
		q = self.myPow(x, p)
		if n % 2 == 0:
			return q * q
		else:
			return q * q * x
	
'''方法3 分治迭代'''
class Solution(object):
	#  时间复杂度:O(log⁡n),即为对 nnn 进行二进制拆分的时间复杂度。
	# 空间复杂度:O(1)O(1)O(1)。

	def myPow(self, x, n):
		ans = 1
		if n < 0:
			x = 1 / x
			n = -n
		elif n == 0:
			return ans
		_x = x	
		while n > 0:
			if n % 2 == 1:
				ans *= _x
			_x *= _x
			n = n // 2
		return ans

结果方法1在测试过程超时,方法2和方法3运行时间和内存消耗都一样,不知道还能怎么减少内存消耗。在这里插入图片描述
在这里插入图片描述

53 最大子序和

问题:

在数组中找到具有最大加和的连续子数组,在我们工作中的应用是历史数据中找出连续的充电段或出行段。不知道算法处理效果怎样,如果能显著提升效率当然可以往这个方向思考改进。

思路

1是贪心算法,2是动态规划,3是分治思路。

  • 贪心算法:原理是最容易想到的,对比累加后比不累加,如果累加和更大则累加,否则不累加,以当前数为起点;
  • 动态规划:原理是累加到前一个数,如果累加值为负则丢弃,从当前值开始累加,如果累加值为正则继续累加;
    复杂度

时间复杂度:O(n)O(n),其中 nn 为 nums 数组的长度。我们只需要遍历一遍数组即可求得答案。
空间复杂度:O(1)O(1)。我们只需要常数空间存放若干变量。

  • 分治思路:数据不断划分为左右子数组,当子数组长度为1时则iSum,lSum,rSum,mSum可得,然后回推到上级,开始合并左右子数组,针对上级左右子数组,iSum为左右iSum的和,lSum可能是左子数组的llSum,也可能延续到右子数组liSum+rlSum,rSum可能是右子数组rrSum,也可能是从左子数组开始lrSum+riSum,我出问题的地方是mSum,可能结果是lmSum或rmSum或者lrSum+rlSum。针对合并过程取各个SUM最大结果。
    假设序列 a 的长度为 n。
方法代码
'''贪心算法'''
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        pre = -inf
        ans = -inf
        for p in nums:
            pre = max(pre + p, p)
            ans = max(pre, ans)
        return ans
       
'''动态规划'''
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        for i in range(1,len(nums)):
            if nums[i-1] > 0:
                nums[i] += nums[i-1]
        return max(nums)

'''分治1'''
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        def iteration(nums):
            if len(nums) == 1:
                iSum = lSum = rSum = mSum = sum(nums)
                return iSum,lSum,rSum,mSum
            else:
                l = 0
                r = len(nums) - 1
                m = (l + r) // 2
                lnums = nums[:(m+1)]
                rnums = nums[(m+1):]
                liSum, llSum,lrSum,lmSum = iteration(lnums)
                riSum, rlSum,rrSum,rmSum = iteration(rnums)
                iSum = liSum + riSum
                lSum = max(llSum, liSum+rlSum)
                rSum = max(rrSum, lrSum+riSum)
                mSum = max(max(lmSum, rmSum), lrSum+rlSum)
                return iSum,lSum,rSum,mSum
        return iteration(nums)[3]
'''分治2 【日后多写这种方式】'''
class Solution:
    def maxSubArray(self, nums):
        def pushup(liSum, llSum,lrSum,lmSum,riSum, rlSum,rrSum,rmSum):
            iSum = liSum + riSum
            lSum = max(llSum, liSum+rlSum)
            rSum = max(rrSum, lrSum+riSum)
            mSum = max(max(lmSum, rmSum), lrSum+rlSum)    
            return iSum,lSum,rSum,mSum
            
        def iteration(nums, l, r):
            if l == r:
                iSum=lSum=rSum=mSum = nums[l]
                return iSum,lSum,rSum,mSum
            else:
                m = (l + r) // 2
                 
                liSum, llSum,lrSum,lmSum = iteration(nums, l, m)
                riSum, rlSum,rrSum,rmSum = iteration(nums, m+1, r)
                return pushup(liSum, llSum,lrSum,lmSum,riSum, rlSum,rrSum,rmSum)
        return iteration(nums, 0, len(nums)-1)[3]

在这里插入图片描述
结果方法1和方法2运行时间和内存消耗都一样,方法3运行时间久。

169 多数元素

问题:

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

思路:

1.中位数是多数元素;2.出现频次最多的是多数元素;3.分治

  • 中位数方法。注意前提是出现次数大于 ⌊ n/2 ⌋ ,所以先将数据排序,中间位置即所求。
  • 计数法。遍历一次数组,计数,再求最高频次的数。
  • 分治方法。数据不断划分为左右子数组,当子数组长度为1时则该可得子数组的最高频の数,然后回推到上级,开始合并左右子数组,合并方法是:左右字数组最高频の数相同则取该值,否则比较各数在原始数组中的频次,取高频の数。递归计算求取最后结果。
方法代码
'''中位数方法'''
class Solution:
    def majorityElement(self, nums: List[int]) -> int:
        nums.sort()
        return nums[len(nums) // 2]

''' 计数法'''
class Solution:
    def majorityElement(self, nums: List[int]) -> int:
        counts = collections.Counter(nums)
        return max(counts.keys(), key=counts.get)

'''分治方法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)

'''分治方法2【哎,效率低】'''
class Solution:
    def majorityElement(self, nums: List[int]) -> int:
        def iteration(nums):
            if len(nums) == 1:
                return nums[0]
            else:
                l = 0
                r = len(nums) 
                m = (l + r) // 2
                lnums = nums[:m]
                rnums = nums[m:]
                l_f = iteration(lnums)
                r_f = iteration(rnums)
                return l_f if sum([1 for i in nums if i==l_f]) > sum([1 for i in nums if i==r_f]) else r_f
        return iteration(nums)

在这里插入图片描述

结果方法1和方法2运行时间和内存消耗基本一样,方法3是官方的分治运行时间久一点;方法4是我写的分治递归,因为左右分子数组序号出错调试了半天,结果也很不理想,用时最久,看来我要学习官方那样的分治递归写法。

总结

本次学习的主要目标是了解分治算法,分治算法的要点在2点:

  1. 把问题分块处理,到最小可处理单元时候,需要明确处理方法。【比如求取幂指数中的累乘,最大子序和中的各Sum求取,多数元素中的最高频の数】
  2. 分块后的合并问题,需要明确处理方法。
    【比如最大子序和中的各Sum求取,多数元素中的最高频の数】
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值