题目:
给你一个 非负 整数数组 nums
和一个整数 k
。如果一个数组中所有元素的按位或运算 OR
的值 至少 为 k
,那么我们称这个数组是 特别的 。请你返回 nums
中 最短特别非空 子数组的长度,如果特别子数组不存在,那么返回 -1
。
示例 1:
输入:nums = [1,2,3], k = 2
输出:1
解释:
子数组 [3]
的按位 OR
值为 3
,所以我们返回 1
。
注意,[2]
也是一个特别子数组。
示例 2:
输入:nums = [2,1,8], k = 10
输出:3
解释:
子数组 [2,1,8]
的按位 OR
值为 11
,所以我们返回 3
。
示例 3:
输入:nums = [1,2], k = 0
输出:1
解释:
子数组 [1]
的按位 OR
值为 1
,所以我们返回 1
。
提示:
1 <= nums.length <= 50
0 <= nums[i] <= 50
0 <= k < 64
思路:
根据或运算的性质可以知道,给定的元素 A 与任意元素 B 进行或运算的结果一定满足 A∣B≥A,由此可以知道对于任意子数组的长度增加元素后按位或运算的结果一定大于等于增加前的结果,满足单调性。由此可知当每次固定数组的右端点时,我们可以使用二分查找或者滑动窗口来找到最短特别子数组的长度。
我们每次用 [left,right] 表示滑动窗口,每次固定右端点 right,如果当前窗口内子数组按位或的结果大于等于 k,此时向右移动左端点 left 直到窗口内子数组的值按位或的结果刚好满足小于 k 为止,此时以 right 为右端点的最短特别子数组长度即为 right−left+1。
为了实时计算当前窗口中子数组或运算的结果,我们需要维护二进制位每一位中 1 出现的次数,如果当前位中 1 的出现的次数为 0,则按位或运算后该位为 0,否则该位则为 1。由于给定数组 nums 中的元素大小不超过 10^9 ,因此最多需要考虑二进制表示的前 30 位。我们需要维护一个长度为 30 的数组 bits,其中 bits[i] 表示滑动窗口中满足二进制表示的从低到高第 i 位的值为 1 的元素个数。
具体计算过程如下:
初始时 left=right=0。每次将滑动窗口的右端点 right 向右移动一位,并更新 bits 数组,通过计算 bits 数组,如果当前窗口内子数组按位或的结果大于等于 k 时,窗口内的子数组一定为特别子数组,则尝试应向右移动左端点 right,并更新 bits 数组。
向右移动左端点直到当窗口内子数组按位或的结果小于 k 或者 left>right 时,此时不再移动左端点 left。在移动窗口的过程中,我们同时更新最短特别子数组长度的长度。
由于可能存在整个数组中所有元素按位或的结果小于 k,此时不存在特别子数组,此时应返回 −1。
代码:
class Solution {
public:
int minimumSubarrayLength(vector<int>& nums, int k) {
int n = nums.size();
vector<int> bits(30);
auto calc = [](vector<int> &bits) -> int {
int ans = 0;
for (int i = 0; i < bits.size(); i++) {
if (bits[i] > 0) {
ans |= 1 << i;
}
}
return ans;
};
int res = INT_MAX;
for (int left = 0, right = 0; right < n; right++) {
for (int i = 0; i < 30; i++) {
bits[i] += (nums[right] >> i) & 1;
}
while (left <= right && calc(bits) >= k) {
res = min(res, right - left + 1);
for (int i = 0; i < 30; i++) {
bits[i] -= (nums[left] >> i) & 1;
}
left++;
}
}
return res == INT_MAX ? -1: res;
}
};