📜  最接近K的子数组的按位与(1)

📅  最后修改于: 2023-12-03 15:40:16.755000             🧑  作者: Mango

最接近K的子数组的按位与

题目描述

给定一个非负整数数组和一个整数k,找到长度最小的子数组,该子数组的按位与值大于等于k。如果不存在这样的子数组,则返回0。

示例1:

输入: [10,5,4,3,7,8], k = 7
输出: 2
解释: 子数组 [4,3] 的按位与值是 4 & 3 = 0b100 & 0b011 = 0b000,该值为4,大于等于7,且长度最小。

示例2:

输入: [1,2,4,0,0,3,2,2,1,0], k = 7
输出: 0
解释: 数组的任何子数组的按位与都小于7,因此返回0。
思路

我们可以发现,对于一个长度为n的数组来说,其所有子数组的个数为$O(n^2)$,所以直接遍历寻找符合条件的子数组是无法通过所有测试用例的。

对于本题,我们可以通过位运算来优化算法。我们来看一下按位与的性质:

  1. 对于非零数x和正整数y,有$x~\texttt{and}~y=x$的充分必要条件是$x\geq y$。
  2. 对于非零数x和y,有$x~\texttt{and}~y=0$的充分必要条件是二进制下x和y的某一位存在一个(或两个)0。

因此,我们可以从高到低枚举每一位的值,如果当前位为0,则不管它,如果当前位为1,则寻找子数组的过程中,直接排除所有该位为0的数,具体而言,从数组第一位开始,一旦找到当前位为0的数,就直接跳过,节省了不少时间。

另外,我们可以使用一个值temp来记录当前子数组的按位与值,如果一个数x与当前的temp按位与值小于k,那么我们可以跳过从该数开始的所有子数组,因为这些子数组的按位与值一定小于k。

最后,我们需要注意一下边界情况,如果遍历完整个数组后,仍没有满足条件的子数组,则返回0。

代码实现
class Solution:
    def minSubArrayLen(self, nums: List[int], k: int) -> int:
        n = len(nums)
        ans = float('inf')
        temp = 0
        for i in range(32, -1, -1):
            # 记录当前子数组的按位与值
            temp |= (1 << i)
            # 表示子数组的右端点
            cur = 0
            # 表示当前子数组的按位与值
            cur_sum = 0
            for j in range(n):
                # 只要该数当前位为0,就跳过
                if (nums[j] & temp) == 0:
                    cur = j + 1
                    cur_sum = 0
                    continue
                # 更新cur_sum
                cur_sum |= nums[j]
                # 如果 cur_sum 大于 k,更新 ans,并缩小子数组范围
                if cur_sum >= k:
                    ans = min(ans, j - cur + 1)
                    break
        return ans if ans != float('inf') else 0
复杂度分析

时间复杂度:$O(n\log^2C)$,其中$C$为数组中的元素值的最大位数。

空间复杂度:$O(1)$。

参考链接