文章目录
题目
给你一个正整数 p 。你有一个下标从 1 1 1 开始的数组 n u m s nums nums ,这个数组包含范围 [ 1 , 2 p − 1 ] [1, 2^p - 1] [1,2p−1] 内所有整数的二进制形式(两端都 包含 )。你可以进行以下操作 任意 次:
从 n u m s nums nums 中选择两个元素 x x x 和 y y y 。
选择 x x x 中的一位与 y y y 对应位置的位交换。对应位置指的是两个整数 相同位置 的二进制位。
比方说,如果 x = 1101 x = 1101 x=1101 且 y = 0011 y = 0011 y=0011 ,交换右边数起第 2 2 2 位后,我们得到 x = 1111 x = 1111 x=1111 和 y = 0001 y = 0001 y=0001 。
请你算出进行以上操作 任意次 以后, n u m s nums nums 能得到的 最小非零 乘积。将乘积对 1 0 9 + 7 10^9 + 7 109+7 取余 后返回。
注意 :答案应为取余 之前 的最小值。
示例 1:
输入:p = 1
输出:1
解释:nums = [1] 。
只有一个元素,所以乘积为该元素。
示例 2:输入:p = 2
输出:6
解释:nums = [01, 10, 11] 。
所有交换要么使乘积变为 0 ,要么乘积与初始乘积相同。
所以,数组乘积 1 * 2 * 3 = 6 已经是最小值。
示例 3:输入:p = 3
输出:1512
解释:nums = [001, 010, 011, 100, 101, 110, 111]
- 第一次操作中,我们交换第二个和第五个元素最左边的数位。
- 结果数组为 [001, 1 10, 011, 100, 001, 110, 111] 。
- 第二次操作中,我们交换第三个和第四个元素中间的数位。
- 结果数组为 [001, 110, 00 1, 110, 001, 110, 111] 。
数组乘积 1 * 6 * 1 * 6 * 1 * 6 * 7 = 1512 是最小乘积。
提示:
1 ≤ p ≤ 60 1 \leq p \leq 60 1≤p≤60
思路
首先我们要明确一点:通过交换位操作,我们希望得到的是一个最小非零乘积。接下来我们来分析一下贪心策略:
对于两个数 x x x和 y y y,如果我们将 y y y中除了最低位以外的所有位都赋值为1,那么交换后的结果将会使得乘积变小且非零。
为什么会这样呢?假设 x x x的参与交换的位是 000 000 000, y y y的参与交换的位是 111 111 111,交换后的结果中, x x x将变成 x + 2 k x+2^k x+2k, y y y将变成 y ′ y' y′,此时, ( x + 2 k ) × y ′ = x × y ′ + y ′ × 2 k (x+2^k) \times y' = x \times y' + y' \times 2^k (x+2k)×y′=x×y′+y′×2k。对比之前的乘积,我们可以发现只要 y ′ < x y'<x y′<x,交换后的乘积就会变小。因此,我们可以不断地将 y y y中的 111 111 111与 x x x中的 000 000 000交换,使得 y y y变成 111 111 111,从而使得乘积变得最小。
基于这个思路,我们可以构造数组 n u m s nums nums,将 [ 1 , 2 p − 1 ] [1, 2^p - 1] [1,2p−1]中的所有整数分为两组:小于 2 p − 1 2^p - 1 2p−1的为一组 A A A,其余的为另一组 B B B。对于 B B B中的元素,除了 2 p − 1 2^p - 1 2p−1以外,其余的数都可以和 A A A中的数一一配对,使得每对的和为 2 p − 1 2^p - 1 2p−1。然后,我们对每一对进行交换操作,使得 B B B中的数变为 111 111 111。这样一来,我们得到了一个数组 n u m s nums nums,其中每一对的乘积都是 2 p − 2 2^p - 2 2p−2,最终的乘积就是 ( 2 p − 1 ) × ( 2 p − 2 ) 2 p − 1 − 1 (2^p - 1) \times (2^p - 2)^{2^{p-1} - 1} (2p−1)×(2p−2)2p−1−1。
代码
c++
class Solution {
int mod = 1000000007;
public: int minNonZeroProduct(int p) {
long x = (1L << p) - 1;
return (int) (myPow((x - 1) % mod, x >> 1) * (x % mod) % mod);
}
long myPow(long a, long b) {
long res = 1;
while (b > 0) {
if ((b & 1) == 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
};
复杂度分析
时间复杂度
构造数组 n u m s nums nums的时间复杂度为 O ( 2 p ) O(2^p) O(2p),其中 2 p 2^p 2p为数组中元素的个数。
快速幂算法在计算 ( 2 p − 2 ) 2 p − 1 − 1 (2^p - 2)^{2^{p-1} - 1} (2p−2)2p−1−1时的时间复杂度为 O ( log n ) O(\log n) O(logn),其中 n = 2 p − 2 n = 2^p - 2 n=2p−2。
因此,总的时间复杂度为 O ( 2 p + log n ) O(2^p + \log n) O(2p+logn)。
空间复杂度
代码中只使用了常数额外的空间,因此空间复杂度为 O ( 1 ) O(1) O(1)。
结果

总结
通过贪心策略和数学推导,我们得到了一个简洁高效的解决方案。贪心策略是通过寻找最小非零乘积的最优交换方式,从而使得乘积变得最小。数学推导则是基于贪心策略得出了最终的乘积公式,通过快速幂算法高效地计算出结果。整体来说,这个解决方案在时间和空间上都表现出了较高的效率。