【Python】 快速幂学习: Leetcode 1969. 数组元素的最小非零乘积

题目描述

给你一个正整数 p 。你有一个下标从 1 开始的数组 nums ,这个数组包含范围 [1, 2^p - 1] 内所有整数的二进制形式(两端都 包含)。你可以进行以下操作任意次:

···从 nums 中选择两个元素 x 和 y 。
···选择 x 中的一位与 y 对应位置的位交换。对应位置指的是两个整数 相同位置 的二进制位。

比方说,如果 x = 1101 且 y = 0011 ,交换右边数起第 2 位后,我们得到 x = 1111 和 y = 0001 。
请你算出进行以上操作 任意次 以后,nums 能得到的 最小非零 乘积。将乘积对 109 + 7 取余 后返回。
注意:答案应为取余 之前 的最小值。

示例 1:
输入:p = 1
输出:1
解释:nums = [1] 。
只有一个元素,所以乘积为该元素。

示例 2:
输入:p = 2
输出:6
解释:nums = [01, 10, 11] 。
所有交换要么使乘积变为 0 ,要么乘积与初始乘积相同。
所以,数组乘积 1 * 2 * 3 = 6 已经是最小值。

思路

首先注意到,因为仅可以交换二进制位,因此,这些元素的和恒定。现求这些元素的乘积,根据基本不等式,ab <= ((a+b) / 2)^2,当a=b时取最大值,当a和b的差越大,即|a-b|越大时,值越小。

那么,需要做的是,元素交换过程中,尽可能拉大他们的差距,尽可能获得更小或更大的元素。换句话来说,在乘积非零的最小的状态下,任意互换元素的两个二进制位,都不会使二者的差距变大。

同时,在2**(p-1)个二进制数中,最低位为1的数量为2**(p-1)个,次高位为1的数量也为2**(p-1)个,类推。

这样一来,可以想象,在2**p - 1个元素的数组上分配这些二进制位,按照以上推论堆放出最小非零乘积。
在这里插入图片描述
可以推出答案为:
在这里插入图片描述

代码

class Solution:
    def minNonZeroProduct(self, p: int) -> int:
        return (2**p-1)*((2**p-2)**(2**(p-1)-1))%(10**9 + 7)

但是以上代码会超时,需要优化幂算法,可以自己实现快速幂:

class Solution:
    def minNonZeroProduct(self, p: int) -> int:
        self.res = 10**9 + 7
        p_pow = 1 << p
        return (p_pow-1)*(self.binpow((p_pow-2)%self.res,(p_pow//2-1)))%self.res
    
    def binpow(self,a, b):
        res = 1
        while b > 0:
            if (b & 1):
                res = res * a
                res %= self.res
            a = a * a
            a %= self.res
            b >>= 1
        return res

注意以上,指数项p也可以取余。根据费马小定理,p可以对(10**9 + 7)-1取余(注意减一)

另一种方法就是用python内置的pow函数,其已做出优化:

class Solution:
    def minNonZeroProduct(self, p: int) -> int:
        mod = 10**9 + 7
        return (2**p - 1) * pow(2**p - 2, 2 ** (p - 1) - 1, mod) % mod
  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值