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

83 lines
3.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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` 从"性能优化"重新分类为"**前置不变式维护**"——它是后续所有逻辑成立的必要条件,而非可选的锦上添花。
### 关联思考
这段教训可以推广到其他算法场景中:**凡是涉及周期性、环形、模运算的问题,第一步的归约预处理往往决定了整个算法的正确性基础**。不要跳过它,也不要事后补它。