leetcode134双周赛 子数组按位与值为 K 的数目「动态规划」「位运算」

3209. 子数组按位与值为 K 的数目

题目描述

给你一个整数数组 nums 和一个整数 k ,请你返回 nums 中有多少个子数组满足:子数组中所有元素按位 AND 的结果为 k

思路1:动态规划

打力扣打PTSD了,看什么题都是DP

状态:表示以结尾,二进制第位连续是1的最长长度

状态转移:

  • 如果的第是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:位运算性质+二分

考虑一个简单问题,给定一个数组,如何求所有(子区间$num[j]$进行与操作后起来)的结果

我们可以对数组进行如下操作:

  • 我们搞一个数组,初始
  • 然后从1开始遍历到n,遍历到1,每次都把与前面所有的$num[j]$进行与操作,即
  • 这样操作会使得每次遍历完,会使得变成
  • 在这个过程中就可以得到所有的子区间数字相与的结果

但是如果仅仅是这样做,只会是标准的复杂度,我们可以想办法优化一下

由于&操作,一定会让数字变小或者不变,所以我们在第二层枚举的时候,可以判断一下如果,说明

也就是说不会使得变小,那我们就可以停止第二层的循环了

我们可以将&操作看作集合的求交集操作,现在说明二进制是1的位置的集合一定是二进制是1的位置的集合的子集,而因为,则二进制是1的位置的集合一定是二进制是1的位置的集合的子集,则集合的大小关系是

那这样&上也一定不变,当然,不是说只有不用&了,小于的也不用&了,证明同上

可以发现,如果想让数字变小,一定是至少有一位二进制位从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;
    }
};

 

博客内容均系原创,未经允许严禁转载!
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇