LeetCode 2411. 按位或最大的最小子数组长度(位运算+滑动窗口)

40 阅读2分钟

题目描述

给定一个非负整数数组 nums,对每一个下标 i,求以 i 为起点的最短子数组 nums[i..j],使得
nums[i] | nums[i+1] | ... | nums[j] 等于 “从 i 开始一直取到数组末尾” 的按位或结果(即整个后缀的按位或结果)。

解题思路

1. 问题分析

  • 目标:对于每个位置 i,找到最短的子数组 nums[i..j],使得子数组的按位或结果等于整个后缀 [i..n-1] 的按位或结果。
  • 难点:直接暴力求解会导致时间复杂度过高(O(n²)),需要寻找更高效的解决方案。

2. 按位运算 + 贪心算法

  • 关键观察:按位或操作是单调不减的,即子数组越长,按位或的结果越大(或不变)。
  • 解决方案:从右往左遍历数组,维护一个数组 pos,记录每个bit位上出现的1的最近下标位置。
    通过贪心算法,具体的,
    如果nums[i]的第bit个二进制位为1,则更新pos[bit],当前bit位上出现的1的最近下标更新为i
    nums[i]的第bit个二进制位为0的时候,如果pos[bit]已经更新过,此时子数组的右边界至少为pos[bit], 动态更新子数组的右端点 j,得到右边界的最小取值。

3. 算法步骤

  1. 初始化

    • pos 数组,长度为 31,初始值为 -1,表示每个位上最近的 1 的位置。
    • ans 数组,长度为 n,用于存储每个位置 i 的最短子数组长度。
  2. 从右往左遍历

    • 对于每个位置 i,初始化 j = i,表示当前子数组的右端点。

    • 遍历 31 个二进制位:

      • 如果 nums[i] 的该位是 1,更新 pos[bit]i
      • 如果 nums[i] 的该位是 0,但之前已经出现过 1(pos[bit] != -1),则必须将子数组延伸到 pos[bit]
    • 更新 ans[i]j - i + 1

  3. 返回结果

    • 返回 ans 数组,其中每个位置 i 的值表示从 i 开始的最短子数组长度。

4. 代码实现

class Solution {
public:
    vector<int> smallestSubarrays(vector<int>& nums) {
        int n = nums.size();
        vector<int> pos(31, -1);  // 记录每个 bit 最左出现 1 的位置
        vector<int> ans(n);

        for (int i = n - 1; i >= 0; --i) {
            int j = i;  // 当前子数组右端点的候选
            for (int bit = 0; bit < 31; ++bit) {
                if (nums[i] >> bit & 1) {
                    // 当前 bit 为 1,更新最左位置
                    pos[bit] = i;
                } else if (pos[bit] != -1) {
                    // 当前 bit 为 0,必须延伸到 pos[bit] 才能置 1
                    j = max(j, pos[bit]);
                }
            }
            ans[i] = j - i + 1;  // 最短长度
        }
        return ans;
    }
};