c++oding="utf-8" ?>
原位循环移动是用o(1)额外空间将数组整体平移k位的操作;标准库std::rotate虽可用,但在某些实现中不保证严格o(1)空间,故嵌入式或面试场景常采用三步翻转法(时间o(n),空间o(1),鲁棒)或环状置换法(更少内存访问但易出错)。

什么是原位循环移动?为什么不能直接用 std::rotate
原位循环移动是指把数组 a[0..n-1] 中的元素整体向左或向右平移 k 个位置,且只用 O(1) 额外空间 —— 也就是“不新建数组、不拷贝临时缓冲区”。std::rotate 确实是标准解法,但它在某些实现中(比如 libc++)会对小数组退化为内存拷贝,且不保证严格 O(1) 空间(内部可能用 memmove 或分段交换)。如果你在嵌入式环境、内存受限场景,或需要确定性行为(比如面试手写),就得自己控制交换逻辑。
三步翻转法:最稳定可靠的原位实现
核心思想是利用“翻转”操作的可逆性和组合性:左移 k 步等价于 —— 先翻转前 n-k 个,再翻转后 k 个,最后翻转整个数组。右移同理,只是顺序微调。它时间 O(n)、空间 O(1)、无分支预测失败风险,且对任意 k(含 k > n)天然鲁棒。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 先对
k取模:k = k % n,避免无效整圈旋转 - 右移
k步对应操作序列:reverse(a, a + n - k)→reverse(a + n - k, a + n)→reverse(a, a + n) - 左移
k步则换成:reverse(a, a + k)→reverse(a + k, a + n)→reverse(a, a + n) - 翻转函数必须是就地、双指针、
O(len)的,别写成递归或额外分配
void reverse(int* a, int* b) {
while (a <h3>环状置换法:节省一次翻转,但要注意下标陷阱</h3><p>当你要极致减少内存访问次数(比如缓存敏感场景),可以用环状置换:把数组看作若干互不相交的循环,每个循环内元素依次跳转 <code>k</code> 步。它只需要 <code>n</code> 次赋值 + <code>gcd(n,k)</code> 次额外变量,比三步翻转少一次完整遍历。但容易出错的点很具体:</p>
-
gcd(n, k)决定了循环总数,不是k本身;用欧几里得算法算,别手写循环求 - 每个循环起点必须是
0到gcd(n,k)-1,不能从0开始硬跑到底,否则会重复处理 - 移动时要用一个临时变量暂存起点值,否则覆盖丢失 —— 这是唯一必需的
O(1)辅助空间 - 若
k == 0或n == 1,必须提前返回,否则gcd计算或循环会出界
示例片段(右移 k):
int g = gcd(n, k); for (int i = 0; i <h3>实际选型建议:别为了“理论上更优”牺牲可读和健壮</h3><p>三步翻转代码短、边界清晰、CPU 流水线友好,几乎所有场景都该优先选它。环状置换只在你知道 <code>n</code> 极大、<code>k</code> 固定、且 profiling 确实卡在内存带宽时才值得引入。另外注意:<code>std::rotate</code> 在 GCC libstdc++ 中就是三步翻转实现,所以直接用它通常没问题;但如果你禁用了 STL 或要跨平台行为一致,就手写翻转函数更稳妥。还有个隐形坑:<code>char</code> 数组做循环移动时,别忘了 <code>std::swap</code> 对 <code>char</code> 是特化的,效率没问题,但自定义类型必须支持移动或拷贝构造 —— 这点常被忽略。</p>
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










