这道LeetCode题目属于位运算系列中的经典问题,要求我们构造一个满足特定条件的最小数组。题目给出一个质数数组nums,要求返回一个数组ans,使得对于每个下标i,满足ans[i] OR (ans[i] + 1) == nums[i],并且在所有可能的解中选择最小的ans[i]。
这个问题的难点在于理解位运算OR的特性以及如何逆向推导出满足条件的最小数值。在实际编程面试中,这类位运算问题经常出现,因为它能很好地考察候选人对二进制操作的理解和数学思维能力。
OR运算(|)是位运算中的基本操作之一,它对两个数的每一位进行逻辑或运算。关键特性包括:
在本题中,我们需要特别关注x OR (x+1)的特殊性质。这个表达式的结果会将x二进制表示中最右边的0变为1,并将该位右侧的所有1变为0。
题目要求我们找到满足x | (x+1) = nums[i]的最小x。我们可以将这个问题转化为:
通过观察我们可以发现,x | (x+1)的结果总是会将x的最右边的0变为1。因此,我们可以逆向思考:给定nums[i],如何恢复出原始的x。
核心算法思路可以分为以下几个步骤:
检查特殊情况:如果nums[i] == 2(唯一的偶质数),直接返回-1,因为x | (x+1)的结果最低位必须是1。
对于其他质数:
这个方法的数学依据是:x | (x+1)的结果中,最右边的0位在x中对应的是1,而该位右边的所有位在x中都是1。通过这种变换,我们可以逆向恢复出最小的x。
以下是各语言实现的详细解析:
java复制class Solution {
public int[] minBitwiseArray(List<Integer> nums) {
int n = nums.size();
int[] ans = new int[n];
for (int i = 0; i < n; i++) {
int x = nums.get(i);
if (x == 2) {
ans[i] = -1;
} else {
int t = ~x;
int lowbit = t & -t;
ans[i] = x ^ (lowbit >> 1);
}
}
return ans;
}
}
Java实现使用了基本的位运算操作,注意Integer的按位取反操作(~)和lowbit的计算方式。
cpp复制class Solution {
public:
vector<int> minBitwiseArray(vector<int>& nums) {
for (int& x : nums) {
if (x == 2) {
x = -1;
} else {
int t = ~x;
x ^= (t & -t) >> 1;
}
}
return nums;
}
};
C++实现利用了引用直接修改原数组,减少了额外的空间开销。
python复制class Solution:
def minBitwiseArray(self, nums: List[int]) -> List[int]:
for i, x in enumerate(nums):
if x == 2:
nums[i] = -1
else:
t = ~x
nums[i] ^= (t & -t) >> 1
return nums
Python实现简洁明了,注意Python中整数的位运算是无限精度的,与Java/C++有所不同。
rust复制impl Solution {
pub fn min_bitwise_array(nums: Vec<i32>) -> Vec<i32> {
let mut ans = Vec::new();
for x in nums {
if x == 2 {
ans.push(-1);
} else {
let t = !x;
let lowbit = t & -t;
ans.push(x ^ (lowbit >> 1));
}
}
ans
}
}
Rust实现体现了其所有权系统的特点,创建新的向量存储结果。
go复制func minBitwiseArray(nums []int) []int {
for i, x := range nums {
if x == 2 {
nums[i] = -1
} else {
t := ^x
nums[i] ^= t & -t >> 1
}
}
return nums
}
Go实现简洁高效,利用了range遍历和位运算。
算法对每个数组元素执行固定次数的位运算操作,因此时间复杂度为O(n),其中n是数组长度。这是最优的时间复杂度,因为我们必须处理每个元素。
大多数实现使用了O(1)的额外空间(除了可能需要存储结果的数组)。在原位修改的实现中(如C++、Python、Go),空间复杂度可以认为是O(1)。
虽然算法已经很高效,但可以考虑以下优化点:
需要考虑的特殊情况包括:
有效的测试案例应包括:
java复制// 示例1
[2,3,5,7] → [-1,1,4,3]
// 示例2
[11,13,31] → [9,12,15]
// 边界案例1:最小输入
[2] → [-1]
// 边界案例2:大质数
[2147483647] → [1073741823]
// 重复元素
[3,3,3] → [1,1,1]
// 混合案例
[2,3,2,5] → [-1,1,-1,4]
通过这道题目,我们可以总结出一些有用的位运算技巧:
这类位运算技巧在实际开发中有广泛应用:
基于这道题目,可以延伸思考以下变种问题:
对于准备技术面试的候选人,这道题目提供了很好的练习机会:
在实际面试中,即使不能立即想到最优解,展示出清晰的思考过程和逐步优化的能力同样重要。