E3314.构造最小位运算数组 I
https://leetcode.cn/problems/construct-the-minimum-bitwise-array-i/
给你一个长度为 n 的质数数组 nums 。你的任务是返回一个长度为 n 的数组 ans ,对于每个下标 i ,以下 条件 均成立:
ans[i] OR (ans[i] + 1) == nums[i]
除此以外,你需要 最小化 结果数组里每一个 ans[i] 。
如果没法找到符合 条件 的 ans[i] ,那么 ans[i] = -1 。
质数 指的是一个大于 1 的自然数,且它只有 1 和自己两个因数。
示例 1:
输入:nums = [2,3,5,7]
输出:[-1,1,4,3]
解释:
- 对于
i = 0,不存在ans[0]满足ans[0] OR (ans[0] + 1) = 2,所以ans[0] = -1。 - 对于
i = 1,满足ans[1] OR (ans[1] + 1) = 3的最小ans[1]为1,因为1 OR (1 + 1) = 3。 - 对于
i = 2,满足ans[2] OR (ans[2] + 1) = 5的最小ans[2]为4,因为4 OR (4 + 1) = 5。 - 对于
i = 3,满足ans[3] OR (ans[3] + 1) = 7的最小ans[3]为3,因为3 OR (3 + 1) = 7。
示例 2:
输入:nums = [11,13,31]
输出:[9,12,15]
解释:
- 对于
i = 0,满足ans[0] OR (ans[0] + 1) = 11的最小ans[0]为9,因为9 OR (9 + 1) = 11。 - 对于
i = 1,满足ans[1] OR (ans[1] + 1) = 13的最小ans[1]为12,因为12 OR (12 + 1) = 13。 - 对于
i = 2,满足ans[2] OR (ans[2] + 1) = 31的最小ans[2]为15,因为15 OR (15 + 1) = 31。
提示:
1 <= nums.length <= 1002 <= nums[i] <= 1000nums[i]是一个质数。
这个问题要求我们找到最小的整数
算法分析
位运算性质: 考察表达式
: - 如果
的二进制表示以 个连续的 结尾(即 ),那么 的二进制表示将会是 。 - 此时
。 - 结果是将
从右往左数第一个 变成 。
- 如果
推导规律: 已知
是质数。 - 如果
(二进制 10):它是唯一的偶质数。因为的结果必然是奇数(末位一定是 ),所以 无法由该表达式得出,返回 。 - 如果
是奇数:它的二进制表示一定以 结尾。为了让 最小,我们需要找到 末尾连续的 。 - 假设
末尾有 个连续的 。 - 根据上面的性质,如果我们把这
个 中最高位的那个 变成 ,得到的数就是满足条件的最小 。 - 例如:
(二进制 111),末尾有个 。将从右数第 位(权值为 )的 变 ,得到 (二进制 011)。 - 验证:
。
- 假设
- 如果
具体步骤:
- 遍历
nums中的每个数。 - 若
,结果为 。 - 否则,从低位向高位查找
的二进制中末尾连续 的个数 。 - 计算
或 。
- 遍历
代码实现
class Solution:
def minBitwiseArray(self, nums: List[int]) -> List[int]:
ans = []
for n in nums:
if n == 2:
ans.append(-1)
else:
# 寻找末尾连续 1 的个数
k = 0
temp = n
while temp & 1:
k += 1
temp >>= 1
# 将第 k-1 位(从0开始)的 1 变为 0
# 例如 n=7 (111), k=3, 结果应为 7 - 2^(3-1) = 3 (011)
ans.append(n - (1 << (k - 1)))
return ans复杂度分析
- 时间复杂度:
,其中 是数组长度。由于 , 最大约为 ,因此计算非常快。 - 空间复杂度:
,除了存储结果所需的数组外,仅使用了常数空间。
class Solution:
def minBitwiseArray(self, nums: List[int]) -> List[int]:
ans = []
for n in nums:
if n == 2:
ans.append(-1)
else:
# n is odd prime >= 3
low = (n + 1) & -(n + 1) # lowest set bit of (n+1)
x = n - low // 2
ans.append(x)
return ans这份代码本质上是利用位运算技巧更高效地寻找末尾连续的 1。
它的核心逻辑和我之前提供的“寻找
个连续的 1”是完全一致的,只不过它避开了 while循环,直接通过位运算公式一步到位。我们可以通过一个具体的例子来拆解这段代码的逻辑。
核心位运算技巧:
x & -x在位运算中,
x & -x是一个经典的技巧,它能提取出整数x二进制表示中最低位的 1(Lowest Set Bit)及其后面的 0。例如:
- 若
(二进制 1100),则12 & -12 = 4(二进制0100)。- 若
(二进制 1000),则8 & -8 = 8(二进制1000)。代码逻辑推导
假设
,其二进制为 1011。
第一步:
n + 1
。 - 二进制变化:
10111100。- 关键点:当一个末尾全是
的数加 时,这些 会全部进位变成 ,而紧邻它们左边的那个 会变成 。 第二步:
low = (n + 1) & -(n + 1)
- 计算
的最低位 。 ( 1100) 的最低位是从右数第三位,权值为 。 - 这个
low(即) 对应的位置,正好是 n(即) 从右往左数第一个 0 的位置。 第三步:
low // 2
low // 2 = 4 // 2 = 2。- 在二进制中,
low // 2就是把那个向右移动一位。 - 这个位置对应的正好是
n(即) 末尾那串连续 中的最高位。 第四步:
x = n - low // 2
。 - 二进制:
1011 - 0010 = 1001。- 这步操作成功地把
n末尾连续中的最高位从 变成了 。 总结对照
概念 你的代码写法 我的代码写法 末尾 1 的个数 无需计算具体个数 循环得到 末尾 1 中最高位的权值 low // 21 << (k - 1)操作目标 将该位的 1 变 0 将该位的 1 变 0 示例走读:
n = 7(二进制111)
n + 1 = 8(二进制1000)low = 8 & -8 = 8(最低位的 1 是 8)low // 2 = 4(对应7的二进制111中左起第一个 1)ans = 7 - 4 = 3(二进制011)- 验证:
。正确。 结论:这份代码使用了
(n+1) & -(n+1)巧妙地定位了需要修改的那一位,避免了循环,在性能上更优(虽然在 LeetCode 这道题的量级下差别不大),是一种更“硬核”的写法。