题目描述
给你一个正整数 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