[算法题]满足子数组异或和小于k的最长子数组

[算法题]满足子数组异或和小于k的最长子数组

今天记录一道字节笔试题,由于没有在其他oj平台上检索到一样的题,遂在此进行记录。

题面:

输入一个数组与一个数k,请找到一个最长的子数组[L, R],使得子数组[L, R]满足:[L, R]的所有子数组的异或和均小于等于k。请输出这个最长子数组的长度。

分析与题解:

这是一个非常经典且有趣的算法题目,结合了异或运算的性质(XOR Properties)与双指针/滑动窗口(Sliding Window)的思想。

  1. 核心结论与思路 满足“子数组 [L,R][L, R] 的所有子子数组的异或和均 k\leq k”这个条件的充分必要条件是:

    1. 该子数组 [L,R][L, R] 中的每一个元素都必须 k\leq k
    2. 该子数组中任意两个位置的前缀异或和(Prefix XOR)的异或结果都必须 k\leq k

由于异或运算不具备像“和”那样的单调性(加一个正数和一定变大,但异或一个数结果可能变小),我们通常使用滑动窗口结合字典树(Trie)来解决这类位运算限制问题。

  1. 详细解题步骤

第一步:转化为前缀异或和 设 P[i]=A[0]A[1]A[i1]P[i] = A[0] \oplus A[1] \oplus \dots \oplus A[i-1]。 那么子数组 [i,j][i, j] 的异或和可以表示为 P[j+1]P[i]P[j+1] \oplus P[i]。 题目要求对于选定的 [L,R][L, R],其内部任意 LijRL \leq i \leq j \leq R 都有: P[j+1] \oplus P[i] \leq k

第二步:滑动窗口与合法性检查 我们可以维护一个窗口 [L,R][L, R],并不断尝试扩大 RR

  • 扩大 RR:当我们将 A[R]A[R] 加入窗口时,需要检查 P[R+1]P[R+1] 与窗口内已有的所有前缀和 P[LR]P[L \dots R] 进行异或,结果是否都 k\leq k
  • 缩小 LL:如果 P[R+1]P[R+1] 与某个 P[i]P[i] (LiRL \leq i \leq R) 异或的结果 >k> k,则说明当前的 LL 太小了,需要右移 LL 直到满足条件。

第三步:使用 0-1 Trie 优化检查 如果直接遍历窗口检查,复杂度会达到 O(n2)O(n^2)。为了高效判断“当前值与窗口内所有值的异或最大值是否 k\leq k”,我们可以将窗口内的前缀和存入一棵 0-1 字典树(Trie)。

  1. 查询:在 Trie 中查找与 P[R+1]P[R+1] 异或能得到的最大值。如果该最大值 k\leq k,则当前窗口合法。
  2. 更新:若不合法,从 Trie 中移除 P[L]P[L],并将 LL 右移,重复查询直到合法。
  3. 插入:将最新的 P[R+1]P[R+1] 插入 Trie。
  1. 复杂度分析
  • 时间复杂度:O(N×log(max(A)))O(N \times \log(\max(A)))。每个数字的前缀异或和在 Trie 中插入和删除各一次,查询一次,位长通常为 30 或 32 位。
  • 空间复杂度:O(N×log(max(A)))O(N \times \log(\max(A))),用于存储 Trie 节点。

使用社交账号登录

  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...