Files
leetcode-go/note/2026-05-17.md
T

3.0 KiB
Raw Blame History

tags, create time
tags create time
daily
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 会触发提前返回。但前提是必须先做取模。

如果你的代码是:

// ❌ 错误顺序:先判断后取模
if k == 0 { return }     // k = n 时不会触发!
k = k % n               // 永远到不了这里

正确的做法是:

// ✅ 正确顺序:先取模后判断
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 从"性能优化"重新分类为"前置不变式维护"——它是后续所有逻辑成立的必要条件,而非可选的锦上添花。

关联思考

这段教训可以推广到其他算法场景中:凡是涉及周期性、环形、模运算的问题,第一步的归约预处理往往决定了整个算法的正确性基础。不要跳过它,也不要事后补它。