最长优雅子数组

标签: 位运算 数组 滑动窗口

难度: Medium

给你一个由 整数组成的数组 nums

如果 nums 的子数组中位于 不同 位置的每对元素按位 与(AND)运算的结果等于 0 ,则称该子数组为 优雅 子数组。

返回 最长 的优雅子数组的长度。

子数组 是数组中的一个 连续 部分。

注意:长度为 1 的子数组始终视作优雅子数组。

示例 1:

输入:nums = [1,3,8,48,10]
输出:3
解释:最长的优雅子数组是 [3,8,48] 。子数组满足题目条件:
- 3 AND 8 = 0
- 3 AND 48 = 0
- 8 AND 48 = 0
可以证明不存在更长的优雅子数组,所以返回 3 。

示例 2:

输入:nums = [3,1,5,11,13]
输出:1
解释:最长的优雅子数组长度为 1 ,任何长度为 1 的子数组都满足题目条件。

提示:

  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 109

Submission

运行时间: 97 ms

内存: 29.1 MB

class Solution:
    def longestNiceSubarray(self, nums: List[int]) -> int:
        l = len(nums)
        res = 1
        left = 0
        mask = 0
        for right in range(l):
            n = nums[right]
            while mask & n:
                mask -= nums[left]
                left += 1
            if right - left + 1 > res:res = right - left + 1 
            mask |= n  
        return res

Explain

这道题解使用了滑动窗口的方法。我们维护一个窗口(由 left 和 right 指针表示),在窗口内所有元素两两进行 AND 操作的结果都必须为 0。通过一个整数 mask 来记录窗口中所有数字的按位或(OR)结果。对于新加入窗口的数字 n,如果 mask 与 n 的 AND 操作结果不为 0,表明 n 与窗口中至少一个数字按位与的结果不为 0,因此需要调整左边界 left 直到 mask 与 n 的 AND 操作结果为 0。窗口扩展和收缩都是线性的,通过这种方式,我们可以高效地找到最长的优雅子数组。

时间复杂度: O(n)

空间复杂度: O(1)

class Solution:
    def longestNiceSubarray(self, nums: List[int]) -> int:
        l = len(nums) # 数组长度
        res = 1 # 最长优雅子数组的初始长度
        left = 0 # 滑动窗口的左边界
        mask = 0 # 记录窗口内所有数字的按位或结果
        for right in range(l): # 遍历数组
            n = nums[right] # 当前考虑加入窗口的元素
            while mask & n: # 当前元素与窗口内元素按位与不为0时
                mask -= nums[left] # 移出窗口左边的元素
                left += 1 # 移动左边界
            if right - left + 1 > res: # 更新最长优雅子数组长度
                res = right - left + 1 
            mask |= n  # 将当前元素加入窗口
        return res # 返回结果

Explore

在滑动窗口中,每次向右移动`right`指针以包含一个新元素时,我们通过`mask |= nums[right]`来更新`mask`。这个操作确保`mask`包含了当前窗口内所有元素的按位或结果。当需要从窗口中移除左侧元素时,应该正确地更新`mask`以反映当前窗口状态,但这需要单独处理每个位,而不是简单地使用减法。

当`mask & n`不为0时,这意味着新考虑的元素`n`与窗口中至少一个元素按位与的结果不为0。移动左边界`left`是为了从窗口中移除元素,直到`mask & n`为0,从而可以安全地将元素`n`加入窗口而不违反条件。右边界`right`代表当前尝试加入到窗口的元素的位置,所以在这种情况下不应该移动`right`,因为我们需要尝试将其纳入窗口中。

`mask -= nums[left]`操作是不正确的,因为按位或操作的逆操作不是简单的减法。正确的方法是重新计算`mask`,即从当前`left`指针的新位置重新对窗口内的所有元素进行按位或操作。另一种更高效的方式是使用额外的数据结构(如计数数组)来跟踪每个位的出现次数,从而动态更新`mask`。

当检测到`mask & n`不为0时,意味着新加入的元素`n`与窗口中至少一个元素的按位与不为0。此时,需要逐步移动左边界`left`,每次移动都从`mask`中移除最左侧元素的贡献,并检查`mask & n`。这一过程重复进行,直到`mask & n`的结果为0,此时窗口中的所有元素与`n`的按位与均为0,使得`n`可以被加入窗口。具体的移动次数取决于窗口中元素的具体值和它们的按位关系。