题目描述
给定一个非负整数数组 nums,对每一个下标 i,求以 i 为起点的最短子数组 nums[i..j],使得
nums[i] | nums[i+1] | ... | nums[j] 等于 “从 i 开始一直取到数组末尾” 的按位或结果(即整个后缀的按位或结果)。
解题思路
1. 问题分析
- 目标:对于每个位置
i,找到最短的子数组nums[i..j],使得子数组的按位或结果等于整个后缀[i..n-1]的按位或结果。 - 难点:直接暴力求解会导致时间复杂度过高(O(n²)),需要寻找更高效的解决方案。
2. 按位运算 + 贪心算法
- 关键观察:按位或操作是单调不减的,即子数组越长,按位或的结果越大(或不变)。
- 解决方案:从右往左遍历数组,维护一个数组
pos,记录每个bit位上出现的1的最近下标位置。
通过贪心算法,具体的,
如果nums[i]的第bit个二进制位为1,则更新pos[bit],当前bit位上出现的1的最近下标更新为i;
当nums[i]的第bit个二进制位为0的时候,如果pos[bit]已经更新过,此时子数组的右边界至少为pos[bit], 动态更新子数组的右端点j,得到右边界的最小取值。
3. 算法步骤
-
初始化:
pos数组,长度为 31,初始值为 -1,表示每个位上最近的 1 的位置。ans数组,长度为n,用于存储每个位置i的最短子数组长度。
-
从右往左遍历:
-
对于每个位置
i,初始化j = i,表示当前子数组的右端点。 -
遍历 31 个二进制位:
- 如果
nums[i]的该位是 1,更新pos[bit]为i。 - 如果
nums[i]的该位是 0,但之前已经出现过 1(pos[bit] != -1),则必须将子数组延伸到pos[bit]。
- 如果
-
更新
ans[i]为j - i + 1。
-
-
返回结果:
- 返回
ans数组,其中每个位置i的值表示从i开始的最短子数组长度。
- 返回
4. 代码实现
class Solution {
public:
vector<int> smallestSubarrays(vector<int>& nums) {
int n = nums.size();
vector<int> pos(31, -1); // 记录每个 bit 最左出现 1 的位置
vector<int> ans(n);
for (int i = n - 1; i >= 0; --i) {
int j = i; // 当前子数组右端点的候选
for (int bit = 0; bit < 31; ++bit) {
if (nums[i] >> bit & 1) {
// 当前 bit 为 1,更新最左位置
pos[bit] = i;
} else if (pos[bit] != -1) {
// 当前 bit 为 0,必须延伸到 pos[bit] 才能置 1
j = max(j, pos[bit]);
}
}
ans[i] = j - i + 1; // 最短长度
}
return ans;
}
};