最小非零乘积:LeetCode 第 1969 题深度解析
📘 题目描述
给你一个正整数 p
,你需要构建一个下标从 1 开始的数组 nums
,其中包含范围 [1, 2^p - 1]
内的所有整数(包括两端),并将这些数字的二进制形式看作可以任意操作的“位块”。
你可以执行如下操作任意次:
- 从
nums
中选择两个元素x
和y
; - 选择它们中对应位置的某一位进行交换(即,如果 x 和 y 的二进制表示在第
i
位上分别是a
和b
,你可以让它们互换这一位的值)。
你的任务是:在任意次此类操作后,使 nums
中所有元素的乘积为最小的非零值,并返回结果对 10^9 + 7
取余后的值。
🧠 解题分析
📌 操作的本质
看似复杂的交换操作,其实是对数组中所有数字的二进制位进行重新分配。你可以无限次交换任意两个数在某一位上的值——这意味着在每个“二进制位”的层面上,所有数位是全局共享的,你可以任意重新分布它们。
关键点是:
- 每一位上有多少个
1
和0
是固定的; - 但你可以把这些
1
和0
分配给任意数字的任意位。
也就是说:在操作完成后,我们可以控制每个数的二进制位,使其构成你希望的任意模式(只要各位上 1 的个数总和不变)。
🎯 解题方法
我们想要使乘积最小化,且必须非零。既然可以控制二进制位的分布,我们要在合法的数字(范围是 [1, 2^p - 1]
)中选择一种组合,使得乘积最小。
🎯 重要观察:
数组 nums
中包含的数字为:
[1,2,3,…,2p−1][1, 2, 3, …, 2^p - 1]
它有总共有 n = 2^p - 1
个元素。
我们做以下几个定义:
max_num = 2^p - 1
是nums
中最大的数,也是所有位都是1
的数字;base = max_num - 1 = 2^p - 2
是第二大的数;- 其余
n - 1 = base
个数可以进行配对。
关键策略:
- 保留一个
max_num
不变; - 剩下的
base = 2^p - 2
个数可以构造成base
,并两两一组(因为总是偶数); - 每组的乘积为
(2^p - 2)^2
,所以总的乘积是:
((2p−2)(2p−2)/2)×(2p−1)\left((2^p - 2){(2p - 2)/2}\right) \times (2^p - 1)
最后对 10^9 + 7
取模即可。
✅ Python 解法
class Solution:
def minNonZeroProduct(self, p: int) -> int:
MOD = 10**9 + 7
max_num = (1 << p) - 1 # 2^p - 1
base = max_num - 1 # 2^p - 2
exp = base // 2 # (2^p - 2) // 2
return (pow(base, exp, MOD) * max_num) % MOD
🧮 示例说明
示例 1:
输入:
p = 1
计算:
- nums = [1]
- 乘积就是 1,自然最小。
输出:1
示例 2:
输入:
p = 2
nums = [1, 2, 3],所有组合要么不能变更,要么保持原始乘积。
- 原始乘积:1 * 2 * 3 = 6
输出:6
示例 3:
输入:
p = 3
- nums = [1, 2, 3, 4, 5, 6, 7]
- max_num = 7
- base = 6,exp = 3
计算:
(6^3 * 7) % MOD = 216 * 7 = 1512
输出:1512
⏱️ 复杂度分析
项目 | 复杂度 |
---|---|
时间复杂度 | O(log p) |
空间复杂度 | O(1) |
快速幂运算复杂度是 O(log e),即指数的位数。
🔍 比较与分析
方法 | 描述 | 复杂度 | 是否满足题意 |
---|---|---|---|
枚举法 | 遍历所有排列,计算乘积 | 指数级 | ❌ 时间超限 |
贪心构造 | 只保留一个最大值,其余统一为次大值 | O(log p) | ✅ 最优解 |
暴力模拟交换位 | 太多无用操作 | 不可行 | ❌ 超时 |
最终,我们用数学归纳 + 快速幂计算找到了这道题的最优解。
🧾 总结
这道题是一道非常典型的数学构造 + 位操作建模问题,核心在于:
- 抽象交换操作的意义;
- 利用二进制位的对称性和全局可控性;
- 构造数学表达式,结合快速幂求解。
不仅锻炼了对位运算和数论的理解,也要求具备较强的建模能力。