题目:
给你一个正整数 p
。你有一个下标从 1 开始的数组 nums
,这个数组包含范围 [1, 2^p - 1]
内所有整数的二进制形式(两端都 包含)。你可以进行以下操作 任意 次:
- 从
nums
中选择两个元素x
和y
。 - 选择
x
中的一位与y
对应位置的位交换。对应位置指的是两个整数 相同位置 的二进制位。
比方说,如果 x = 1101
且 y = 0011
,交换右边数起第 2
位后,我们得到 x = 1111
和 y = 0001
。
请你算出进行以上操作 任意次 以后,nums
能得到的 最小非零 乘积。将乘积对 109 + 7
取余 后返回。
注意:“最小值”应为取余 之前 的最小值。
思考:
“选择 x
中的一位与 y
对应位置的位交换” ----> (只有对应位置的位一个是0,一个是1的情况下,交换才有意义)所以题意即x减去2^k,y加上2^k
“可以进行交换操作任意次,使得数组内所有元素的乘积最小(不为0)” ----> 对于若干个数,每次取两个数,进行一个数减去2^k,另一个数加上2^k的操作,直到所有数相乘最小 ----> ?
数学分析:
1. 对于若干个数,如何选择交换的两个数?(即选择哪个数加,选择哪个数减?)
以a,b(a<b)两个数,分别加/减1为例:
原乘积=ab,选一个数加1后:情况一,乘积=(a+1)b=原乘积+b;情况二,乘积=(b+1)a=原乘积+a,显然,情况二乘积更小。选一个数减1后:情况一,乘积=(a-1)b=原乘积-b;情况二,乘积=(b-1)a=原乘积-a,显然,情况一乘积更小。----> 选择更小的数减一,选择更大的数加一
为了验证这一推论,再以a,b,c(a<b<c)三个数,选两个数分别加/减1为例:
原乘积=abc,选一个数加1后:情况一,乘积=(a+1)bc=原乘积+bc;情况二,乘积=(b+1)ac=原乘积+ac;情况三,乘积=(c+1)ab=原乘积+ab,显然,情况三乘积最小。选一个数减1后:情况一,乘积=(a-1)bc=原乘积-bc;情况二,乘积=(b-1)ac=原乘积-ac;情况三,乘积=(c-1)ab=原乘积-ab,显然,情况一乘积最小。----> 选择最小的数减一,选择最大的数加一,推论得到验证
2. 加/减多少才能使总乘积最小?(即加/减n,n应该取值多少?)
还是以a,b,c(a<b<c)三个数,选两个数分别加/减n为例:
原乘积=abc,选择最小数a减n,最大数c加n,乘积
=(a-n)b(c+n)
=abc+abn-nbc-n²
=abc+n[b(a-c)-n],
(a-c) < 0 ----> n[b(a-c)-n] < 0 ----> abc+n[b(a-c)-n] < abc
所以,只要进行加/减n(n>0)操作,乘积一定会变小,直至有数变到0为止。也就是说,n在满足a-n>0的条件下尽可能取大的值,所以n=a-1
3.综合1和2,分析数组[1,2,3,......,2^p-3,2^p-2,2^p-1]
数组一共有2^p-1个元素,即奇数个元素,那么按照常规思路两两交换后,会剩下中间一个数无法进行交换操作,这样得到的乘积也不是最小的。
那如何将考虑对象变成偶数个元素呢?
2^p-1的二进制全为1,因此没有为0的位可供交换,所以将最后一个元素2^p-1刨除不看,以1和2^p-2对应,2和2^p-3对应......的关系,两两对应并进行互换操作(即加/减2^k)。
本来因为乘积不能为0,是不能将最小数减到0的,但是为了方便计算,我们将最小数都先减到0,然后对所有0进行+1操作,对所有最大值进行-1操作。
对于1和2^p-2:数组变为[0,2,3,......,2^p-3,2^p-1,2^p-1]
对于2和2^p-3:数组变为[0,0,3,......,2^p-1,2^p-1,2^p-1]
......
到最后,数组变为[0,0,0,......,2^p-1,2^p-1,2^p-1],其中0有2^(p-1)-1个,2^p-1有2^(p-1)个
再对所有0进行+1操作,对所有最大值进行-1操作,原来刨除的最后一个数不变,数组变为[1,1,1,......,2^p-2,2^p-2,2^p-1],其中1有2^(p-1)-1个,2^p-2有2^(p-1)-1个,2^p-1有1个
此时计算数组的所有元素的乘积=1* (2^p-2) ^ (2^(p-1)-1) * (2^p-1)
代码如下:
class Solution(object):
def minNonZeroProduct(self, p):
"""
:type p: int
:rtype: int
"""
if p ==1:
return 1
mod = 10**9 + 7
answer = (2**p-2)**(2**(p-1)-1) * (2**p-1) %mod
return answer
测试通过,但提交发现超时了 :
查看题解之后懂了,因为幂数太大了,所以用这个代码求幂太慢,得用快速求幂,python的快速求幂函数如下:
base = 2
exponent = 3
result = pow(base, exponent) # base是底数,exponent是指数
print(result) # 输出: 8
修改原代码如下:
class Solution(object):
def minNonZeroProduct(self, p):
"""
:type p: int
:rtype: int
"""
if p ==1:
return 1
mod = 10**9 + 7
base = 2**p-2
exponent = (2**(p-1)-1)
answer = pow(base, exponent, mod) * (2**p-1) %mod
return answer
提交通过: