--- tags: [daily] create time: 2026-05-17 00:00 --- # 2026-05-17 ## 今日学习 - [[15-轮转数组]] — 预处理 `k %= n` 的深层意义:不仅仅是去冗余,更是语义归约 --- ## 错题反思:我对 `k = k % n` 的理解还不够透彻 在做轮转数组(LeetCode 189)时,我在 `k = k % n` 这一步上栽了跟头。这行代码看似简单到"不需要解释",但恰恰是这种"理所当然"的操作最容易在关键时刻掉链子。 ### ❌ 我错在哪里? > [!warning] ⚠️ 问题核心 我把 `k = k % n` 仅仅当作一个**性能优化**——"如果 k 很大就减掉几圈"。但这种理解是不完整的,甚至可以说是在某些边界场景下**错误的**。 ### 📐 正确认知:这是语义归约,不是性能优化 `k = k % n` 的存在有三层含义,缺一不可: #### 第一层:功能正确性(最容易被忽略) > [!question] 💡 如果 k < n,这行代码还有必要吗? 有。因为在循环替换法中,如果你在外面做了 `if k == 0 { return }` 的判断,那么当 `k = n` 时(恰好等于长度),`k % n == 0` 会触发提前返回。**但前提是必须先做取模**。 如果你的代码是: ```go // ❌ 错误顺序:先判断后取模 if k == 0 { return } // k = n 时不会触发! k = k % n // 永远到不了这里 ``` 正确的做法是: ```go // ✅ 正确顺序:先取模后判断 k = k % n // 先把所有等价类归约到 [0, n) if k == 0 { return } // 再检查是否真的无需操作 ``` #### 第二层:安全防越界 > [!note] 🔒 为什么后续计算依赖归约后的 k? 以反转法为例,三个反转的区间是 `[0, n-1]`, `[0, k-1]`, `[k, n-1]`。如果 `k > n`: - `reverse(nums, 0, k-1)` → 右边界超出数组长度的 **k-n** 位 → **越界 panic**! - `reverse(nums, k, n-1)` → 左边界直接越界 → **同样 panic**! `% n` 确保了 k 始终落在 `[0, n)` 范围内,使后续所有区间计算天然安全。 #### 第三层:数学对称性 > [!abstract] 🧮 更深层的意义 轮转操作在数学上构成一个**循环群 Z_n**。在这个群中,旋转 n 步等于恒等变换(什么都不做)。`k % n` 的本质是将任意整数 k **映射到同一个陪集代表元**: ``` Z_n = {0, 1, 2, ..., n-1} k ≡ (k % n) (mod n) ``` 这意味着:**旋转 k 步和旋转 (k + m·n) 步对于任意整数 m 效果完全相同**。取模运算将无限多个等价的 k 值压缩到一个唯一的代表元。 这就是为什么它不仅是一个工程技巧,而是一个**数学必然**。 ### 🎯 一句话修正 > 把 `k %= n` 从"性能优化"重新分类为"**前置不变式维护**"——它是后续所有逻辑成立的必要条件,而非可选的锦上添花。 ### 关联思考 这段教训可以推广到其他算法场景中:**凡是涉及周期性、环形、模运算的问题,第一步的归约预处理往往决定了整个算法的正确性基础**。不要跳过它,也不要事后补它。