题目描述
给你一个正整数 p
。你有一个下标从 1 开始的数组 nums
,这个数组包含范围 [1, 2^p - 1]
内所有整数的二进制形式(两端都 包含)。你可以进行以下操作 任意 次:
- 从
nums
中选择两个元素x
和y
。 - 选择
x
中的一位与y
对应位置的位交换。对应位置指的是两个整数 相同位置 的二进制位。
比方说,如果 x = 1101
且 y = 0011
,交换右边数起第 2
位后,我们得到 x = 1111
和 y = 0001
。
请你算出进行以上操作 任意次 以后,nums
能得到的 最小非零 乘积。将乘积对 10^9 + 7
取余 后返回。
注意:答案应为取余 之前 的最小值。
1.思路
我们先思考一个简单的问题,假设给定三个正整数 a,b,c,满足 a<b<c,此时需要将某个数缩小 1,另外一个数增加 1,使得 abc 乘积最小,如何选择才是最优选择?
缩小时优先缩小最小的元素:当选择 a 缩小 1 时,此时三者乘积为 (a−1)bc,整体较 abc 缩小了 bc;当选择缩小 c 时,三者乘积为 ab(c−1),整体较 abc 缩小了 ab,此时 ab<bc,因此缩小时优先缩小最小的元素;
增加时优先增加最大的元素:缩小 a 然后选择某个元素进行增加,当选择 b 增加 1 时,此时三者乘积为 (a−1)(b+1)c,整体较 (a−1)bc 增加了 (a−1)c(a-1)c(a−1)c;同理,当选择增加 c 时,增加了 (a−1)b,此时 (a−1)b<(a−1)c,因此增加时优先增加最大的元素。
回到本题,两个数在进行相同的位交换时,本质即将一个元素缩小 2^k,另外一个元素增加 2^k 。根据上述分析,我们可以知道一种贪心思路:进行相同位交换时,优先缩小数组中最小的元素,再增加数组中最大的元素。设当前数组为:[1,2,3,……,2^p-2,2^p-1]。
将1与2^p-1做-(1-1)与+(1-1)操作,2与2^p-3做-(2-1)与+(2-1)操作,……,2^(p-1)-1与2^(p-1)做-(2^(p-1)-1-1)与+(2^(p-1)-1-1)操作,最终得到数组:
[1,1,……,2^p-2,……,2^p-2,2^p-1],乘积为
2.代码
由于幂过大,可以采用快速幂算法进行运算。(读者可以自行学习)
public int minNonZeroProduct(int p) {
if(p==1)return 1;
long mod=1000000007;
long x= fastPow(2,p,mod)-1;
long y=(long) 1 << (p-1); //不可使用fastPow()方法进行计算,因为该方法会对结果进行模运算
return (int) ((fastPow(x-1,y-1,mod)*x)%mod);
}
public long fastPow(long x,long y,long mod){ //快速幂算法,x为底数,y为指数,mod为取模数
long ans=1;
long a=x;
while (y>0){
if((y&1)!=0)ans=(ans*a)%mod;
a=a*a%mod;
y>>=1;
}
return ans;
}