83 lines
3.0 KiB
Markdown
83 lines
3.0 KiB
Markdown
|
|
---
|
|||
|
|
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` 从"性能优化"重新分类为"**前置不变式维护**"——它是后续所有逻辑成立的必要条件,而非可选的锦上添花。
|
|||
|
|
|
|||
|
|
### 关联思考
|
|||
|
|
|
|||
|
|
这段教训可以推广到其他算法场景中:**凡是涉及周期性、环形、模运算的问题,第一步的归约预处理往往决定了整个算法的正确性基础**。不要跳过它,也不要事后补它。
|