文章目录
- 题目
- 思路
- 代码
- 复杂度分析
- 时间复杂度
- 空间复杂度
- 结果
- 总结
题目
题目链接🔗
给你一个正整数 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, 110, 011, 100, 001, 110, 111] 。
- 第二次操作中,我们交换第三个和第四个元素中间的数位。
- 结果数组为 [001, 110, 001, 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。
代码
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)。
结果
总结
通过贪心策略和数学推导,我们得到了一个简洁高效的解决方案。贪心策略是通过寻找最小非零乘积的最优交换方式,从而使得乘积变得最小。数学推导则是基于贪心策略得出了最终的乘积公式,通过快速幂算法高效地计算出结果。整体来说,这个解决方案在时间和空间上都表现出了较高的效率。