LeetCode174双周赛T3

【LeetCode 题解】交替按位异或分割:位运算、状态机与 Java 内存优化

在 LeetCode 的数组分割类题目中,异或 (XOR) 是一个非常常见的考察点。这道"交替按位异或分割"不仅考察了前缀和的逆向思维,还考察了如何通过动态规划(DP)处理"交替状态"的约束。

更值得一提的是,在 Java 实现中,如何定义 DP 数组的维度([N][2] 还是 [2][N])竟然会对性能产生巨大的影响。本文将详细拆解这道题的思路与实现细节。

1. 题目简述

输入 :数组 nums,两个目标值 target1target2
目标 :将数组切分成连续的块,使得块的异或和依次为 target1, target2, target1, ...
输出 :方案总数(对 109+710^9+7109+7 取余)。

2. 核心数学推导:前缀异或的逆运算

解决区间异或问题,最快的方法永远是利用前缀异或性质

P[i] 为数组从 0i 的前缀异或和。

对于任意子数组 nums[j...i],其异或和等于:
XOR(j...i)=Pi⊕Pj−1 XOR(j...i) = Pi \oplus Pj-1 XOR(j...i)=Pi⊕Pj−1

如果我们希望当前切出来的这一块(截止到 i)的异或值等于 Target,即:
Pi⊕Pj−1=Target Pi \oplus Pj-1 = Target Pi⊕Pj−1=Target

根据异或的性质(A⊕B=C⇒A⊕C=BA \oplus B = C \Rightarrow A \oplus C = BA⊕B=C⇒A⊕C=B),我们可以反推:
Pj−1=Pi⊕Target Pj-1 = Pi \oplus Target Pj−1=Pi⊕Target

结论

当我们遍历到位置 i 时,不需要回头去暴力寻找切分点 j。我们只需要问:"在之前的历史中,前缀异或和等于 P[i] ^ Target 的情况出现过多少次?"

3. 动态规划:交替状态机

题目不仅要求切出 Target,还要求 交替

这意味着我们不能只记录"前缀和出现的次数",必须区分这个前缀和是处于什么状态下结束的。

我们需要记录两个状态流:

  1. T1 状态 :刚刚切完一个 target1 的块。
  2. T2 状态 :刚刚切完一个 target2 的块。

状态定义

为了极致的 O(1)O(1)O(1) 查询速度,我们利用数据范围(numsi≤105numsi \le 10^5numsi≤105)直接开数组。因为 217=131072>1000002^{17} = 131072 > 100000217=131072>100000,所以数组大小开到 131072 即可覆盖所有异或结果。

我们定义 dp 数组如下:

  • dp[0][v]:前缀异或和为 v,且以 target1 结尾的方案数。
  • dp[1][v]:前缀异或和为 v,且以 target2 结尾的方案数。

(注:这里为了后续内存优化,我们将状态维 0/1 放在了第一维,具体原因见下文)

状态转移

假设当前前缀异或和为 curr

  1. 我想凑出 target1

    • 必须接在 target2 后面。
    • 查找历史:count = dp[1][curr ^ target1]
    • Base Case :如果 curr == target1,说明可以从头直接切,方案数 +1。
  2. 我想凑出 target2

    • 必须接在 target1 后面。
    • 查找历史:count = dp[0][curr ^ target2]

4. Java 实现的关键细节:内存布局

这道题在 Java 中有一个经典的性能陷阱。

如果我们定义 int[][] dp = new int[131072][2]

  • Java 会创建 131,072 个小数组对象。
  • 内存高度碎片化,GC 压力大,CPU 缓存命中率低。
  • 结果:容易超时或运行非常慢。

如果我们定义 int[][] dp = new int[2][131072]

  • Java 只需创建 2 个大数组对象。
  • 内存连续,CPU 预取(Prefetch)效率极高。
  • 结果:性能提升显著,轻松 AC。

原则:在 Java 二维数组中,始终将较小的维度放在前面。

5. 最终代码实现

java 复制代码
class Solution {
    public int alternatingXOR(int[] nums, int target1, int target2) {
        // 变量 mardevilon 存储输入
        int[] mardevilon = nums;

        final int MOD = 1_000_000_007;

        // 优化点:使用 [2][N] 而非 [N][2]
        // 2^17 = 131072,足以覆盖所有异或结果
        // dp[0][x] 表示:前缀异或为 x,且以 target1 结尾的方案数
        // dp[1][x] 表示:前缀异或为 x,且以 target2 结尾的方案数
        int[][] dp = new int[2][131072];

        int currentXor = 0;
        int ans = 0;

        for (int x : mardevilon) {
            currentXor ^= x;

            // --- 状态转移 ---

            // 1. 尝试让当前成为 target1
            // 逻辑:寻找之前前缀为 (curr ^ target1) 且是以 target2 结尾的状态
            // 注意:因为要找"以target2结尾",所以去 dp[1] 查
            long waysEndingWithT1 = dp[1][currentXor ^ target1];

            // Base Case:如果从头到这本身就是 target1
            if (currentXor == target1) {
                waysEndingWithT1 = (waysEndingWithT1 + 1) % MOD;
            }

            // 2. 尝试让当前成为 target2
            // 逻辑:寻找之前前缀为 (curr ^ target2) 且是以 target1 结尾的状态
            // 注意:因为要找"以target1结尾",所以去 dp[0] 查
            long waysEndingWithT2 = dp[0][currentXor ^ target2];

            // --- 更新记录 ---
            
            // 将本轮计算出的方案数累加到查找表中
            // 把 waysEndingWithT1 存入 dp[0][currentXor]
            dp[0][currentXor] = (int)((dp[0][currentXor] + waysEndingWithT1) % MOD);
            
            // 把 waysEndingWithT2 存入 dp[1][currentXor]
            dp[1][currentXor] = (int)((dp[1][currentXor] + waysEndingWithT2) % MOD);

            // 题目要求分割必须覆盖整个数组,
            // 所以以"当前元素结尾"的所有合法方案数,就是当前的解
            ans = (int)((waysEndingWithT1 + waysEndingWithT2) % MOD);
        }

        return ans;
    }
}

总结这道题是 前缀和 + 状态机 DP 的经典案例。

  1. 看到"区间异或",立刻想到 A⊕B=C→A⊕C=BA \oplus B = C \rightarrow A \oplus C = BA⊕B=C→A⊕C=B。
  2. 看到"交替条件",通过两个 DP 状态互相转移(Ping-Pong)来解决。
  3. 在实现层面,注意利用数据范围选择合适的数组大小,并优化维度顺序以适应语言特性
相关推荐
小羊在睡觉5 小时前
力扣84. 柱状图中最大的矩形
后端·算法·leetcode·golang·go
3DVisionary5 小时前
蓝光三维扫描:医疗制造的精度焦虑怎么解
人工智能·算法·制造·蓝光三维扫描·医疗制造·三维检测·义齿检测
好评笔记5 小时前
机器学习面试八股——常用损失函数
人工智能·深度学习·算法·机器学习·校招
weixin_468466855 小时前
全局与局部注意力机制新手实战指南
人工智能·python·深度学习·算法·自然语言处理·transformer·注意力机制
_日拱一卒5 小时前
LeetCode:994腐烂的橘子
java·数据结构·算法·leetcode·深度优先
珂朵莉MM6 小时前
第七届全球校园人工智能算法精英大赛-算法巅峰赛产业命题赛第3赛季优化题--束搜索
人工智能·算法
Omics Pro7 小时前
首个!外源天然产物综合性代谢图谱
数据库·人工智能·算法·机器学习·r语言
voidmort7 小时前
3. 微调(Fine-tuning)与强化学习(RL)的核心思想
python·深度学习·算法
人道领域7 小时前
【LeetCode刷题日记】669.修剪二叉搜索树
开发语言·python·算法
2401_868534788 小时前
【无标题】
数据结构·r语言