3209. 子数组按位与值为 K 的数目
题目描述
给你一个整数数组 nums
和一个整数 k
,请你返回 nums
中有多少个子数组满足:子数组中所有元素按位 AND
的结果为 k
思路1:动态规划
打力扣打PTSD了,看什么题都是DP
状态:
状态转移:
- 如果
的第 是1,则 - 否则,
有了状态以后,怎么计算答案?
我们可以枚举子数组的右边界
- 如果
的第 位是1,根据&运算的性质,则满足条件的子数组的每个数的第 位都应该是1。所以我们对k的每一个是1的位,求对应 的最小值,计作minx,求最小值的原因是,我们要求出满足这个子区间所有k是1的位都是1的最短长度,这是上界,不能再往前找了,因为只要再往前一位,一定存在一位本该是1的位置&上了0,变成了0,使得区间&起来不是k - 如果k的第
位是0,根据&运算的性质,则满足条件的子数组的第 位应该至少有一个0,所以我们对k的每一个是0的位,求对应 的最大值,计作maxn,求最大值的原因是, 代表第 的第 位往前连续的1的最长长度,我们现在要保证有一个0,则往前的第一个0是位于往前 的位置,这是下界,是可以再往前找的,因为前面不管是什么,我们是0的位置&起来都是0 - 所以答案就是
对于每个右边界
class Solution {
public:
long long countSubarrays(vector<int>& nums, int k) {
int n = nums.size();
vector<vector<int>>dp(n + 1, vector<int>(32));
long long ans = 0;
for(int i = 1; i <= nums.size(); ++i){
for(int j = 0; j < 32; ++j){
if((nums[i - 1]>>j) & 1)dp[i][j] = dp[i - 1][j] + 1;
else dp[i][j] = 0;
}
int minx = i, maxn = 0;
for(int j = 0; j < 32; ++j){
if((k>>j)&1)minx = min(minx, dp[i][j]);
else maxn = max(maxn, dp[i][j]);
}
cout<< minx << " " << maxn << endl;
ans += max(0, minx - maxn);
}
return ans;
}
};
思路2:位运算性质+二分
考虑一个简单问题,给定一个数组,如何求所有(子区间
我们可以对数组进行如下操作:
- 我们搞一个
数组,初始 - 然后
从1开始遍历到n, 从 遍历到1,每次都把 与前面所有的$num[j]$进行与操作,即 - 这样操作会使得每次遍历完
,会使得 变成 - 在这个过程中就可以得到所有的子区间数字相与的结果
但是如果仅仅是这样做,只会是标准的
由于&操作,一定会让数字变小或者不变,所以我们在第二层枚举的时候,可以判断一下如果
也就是说
我们可以将&操作看作集合的求交集操作,现在
那这样
可以发现,如果想让数字变小,一定是至少有一位二进制位从1变成0,由于本题二进制下1最多30位,所以最坏的时间也就是
现在回到本题,本题求的是子区间&起来等于k的数量,我们只需要在循环
class Solution {
public:
long long countSubarrays(vector<int>& nums, int k) {
int n = nums.size();
long long ans = 0;
for(int i = 0; i < n; ++i){
for(int j = i - 1; j >= 0; --j){
if((nums[j] & nums[i]) == nums[j]){
break;
}
nums[j] &= nums[i];
}
int l = lower_bound(nums.begin(), nums.begin() + i + 1, k) - nums.begin();
int r = upper_bound(nums.begin(), nums.begin() + i + 1, k) - nums.begin();
ans += r - l;
}
return ans;
}
};