news 2026/2/12 8:22:51

LeetCode “热题 100” 轮转数组(Rotate Array)终极解析:三种 O(n) 解法深度剖析与工程实践指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LeetCode “热题 100” 轮转数组(Rotate Array)终极解析:三种 O(n) 解法深度剖析与工程实践指南

🔁 轮转数组(Rotate Array)终极解析:三种 O(n) 解法深度剖析与工程实践指南

字数:约 9500 字(不含代码块)
关键词:LeetCode 189、轮转数组、原地算法、数组翻转、环状替换、Java 算法、空间复杂度 O(1)


在 LeetCode “热题 100” 中,轮转数组(Rotate Array)是一道兼具基础性与挑战性的经典题目。它不仅是考察数组操作能力的“试金石”,更是检验候选人对原地算法设计、数学建模能力与工程思维的综合考题。

本文将为你带来一场系统化、结构化、实战化的深度解析。我们将从问题本质出发,逐步推导出三种时间复杂度为 O(n) 的解法,并深入探讨其背后的数学原理、代码实现细节、性能对比、面试策略及实际应用场景。无论你是算法初学者、面试冲刺者,还是希望提升代码质量的工程师,本文都将提供可落地、可复用、可迁移的知识体系。

💡阅读建议:先尝试独立思考本题,再结合本文对比思路;重点掌握方法三(数组翻转),它是面试中最推荐的解法。


一、题目回顾:精准理解问题边界

1.1 原题描述(LeetCode 189)

给定一个整数数组nums,将数组中的元素向右轮转k个位置,其中k是非负整数。

示例 1:
输入: nums = [1,2,3,4,5,6,7], k = 3 输出: [5,6,7,1,2,3,4] 解释: - 向右轮转 1 步 → [7,1,2,3,4,5,6] - 向右轮转 2 步 → [6,7,1,2,3,4,5] - 向右轮转 3 步 → [5,6,7,1,2,3,4] ✅
示例 2:
输入:nums = [-1,-100,3,99], k = 2 输出:[3,99,-1,-100]

1.2 约束条件(关键!)

条件说明
1 <= nums.length <= 10^5数组规模大,O(n²) 算法会超时
-2³¹ <= nums[i] <= 2³¹ - 1元素为 32 位整数,无需特殊处理溢出
0 <= k <= 10^5k可能远大于n,必须取模优化

1.3 进阶要求(面试高频考点)

  1. 至少提出三种不同解法
  2. 能否实现空间复杂度为 O(1) 的原地算法?

⚠️注意:题目要求原地修改nums,不能返回新数组!


二、问题分析:抓住核心矛盾

2.1 什么是“轮转”?

“向右轮转k位”等价于:

  • 将数组末尾的r = k % n个元素移动到开头;
  • 剩余n - r个元素整体后移r位。

即:
原数组:[A₁, A₂, ..., Aₙ₋ᵣ, B₁, B₂, ..., Bᵣ]
目标数组:[B₁, B₂, ..., Bᵣ, A₁, A₂, ..., Aₙ₋ᵣ]

2.2 核心挑战

挑战说明
元素覆盖直接赋值会覆盖未处理元素(如nums[i] = nums[i-k]
k 过大k ≥ n时需取模,否则逻辑错误或越界
空间限制要求 O(1) 空间,禁止新建数组
效率要求O(nk) 解法(逐次移动)在n=1e5, k=1e5时超时(10¹⁰ 操作)

2.3 解题突破口

  • 映射关系i → (i + k) % n
  • 循环结构:数组可被划分为若干不相交的循环链
  • 对称操作:翻转具有“逆序+重组”的特性,可巧妙构造目标序列

三、解法一:辅助数组法(直观但非原地)

3.1 思路详解

创建一个新数组newArr,根据映射关系newArr[(i + k) % n] = nums[i]填充,最后拷贝回原数组。

✅ 优点
  • 逻辑清晰,零理解成本;
  • 时间复杂度最优(O(n))。
❌ 缺点
  • 空间复杂度 O(n),不满足“原地”要求;
  • 在内存受限场景(如嵌入式系统)不可用。

3.2 Java 实现(规范格式)

classSolution{/** * 方法一:使用额外数组 * 时间复杂度:O(n) * 空间复杂度:O(n) */publicvoidrotate(int[]nums,intk){intn=nums.length;int[]newArr=newint[n];// 将每个元素放到新位置for(inti=0;i<n;i++){newArr[(i+k)%n]=nums[i];}// 拷贝回原数组(高效方式)System.arraycopy(newArr,0,nums,0,n);}}

💡小贴士:使用System.arraycopy()比手动 for 循环拷贝更快,因 JVM 对其做了底层优化。

3.3 调试技巧

  • 测试边界k = 0k = nk > n
  • 验证映射:打印(i, (i+k)%n)对,确保无遗漏或重复。

四、解法二:环状替换法(数学之美,原地 O(1))

4.1 核心思想

不使用额外空间,通过链式交换完成轮转:

  1. 从索引start开始,保存nums[start]
  2. 计算目标位置next = (current + k) % n
  3. temp保存nums[next],将prev写入nums[next]
  4. 更新prev = temp,current = next,继续直到回到start

但一次循环可能无法覆盖所有元素,需启动多个循环。

4.2 关键数学原理

定理:循环次数 = gcd(n, k)

证明简述

  • 设单次循环访问L个元素,则L × k ≡ 0 (mod n)
  • 最小正整数解为L = n / gcd(n, k)
  • 故总循环次数 =n / L = gcd(n, k)
示例:n=6, k=2
  • gcd(6,2)=2,存在两个循环链:
    • 链1:0 → 2 → 4 → 0
    • 链2:1 → 3 → 5 → 1

4.3 Java 实现(含注释)

classSolution{/** * 方法二:环状替换(Cyclic Replacements) * 时间复杂度:O(n) —— 每个元素仅访问一次 * 空间复杂度:O(1) */publicvoidrotate(int[]nums,intk){intn=nums.length;k%=n;// 关键:处理 k >= n 的情况// 循环次数 = gcd(n, k)intcycles=gcd(n,k);// 启动 cycles 个独立循环for(intstart=0;start<cycles;start++){intcurrent=start;intprev=nums[start];// 保存起始值do{intnext=(current+k)%n;inttemp=nums[next];// 保存将被覆盖的值nums[next]=prev;// 写入正确值prev=temp;// 更新 prev 为被覆盖的值current=next;// 移动到下一位置}while(current!=start);// 直到回到起点}}/** * 欧几里得算法求最大公约数(递归版) * 时间复杂度:O(log(min(a, b))) */privateintgcd(inta,intb){returnb==0?a:gcd(b,a%b);}}

⚠️注意

  • 必须先对k取模,否则k过大会导致逻辑错误;
  • 使用do-while确保至少执行一次(避免k=0时死循环)。

4.4 调试建议

  • 可视化循环链:打印每次current → next的路径;
  • 验证 gcd:手动计算gcd(n,k),确认循环次数正确。

五、解法三:数组翻转法(优雅高效,强烈推荐)

5.1 思路揭秘:三次翻转的魔法

利用翻转的“逆序重组”特性,分三步实现轮转:

步骤操作效果
1翻转整个数组[A B] → [B' A']
2翻转前r个元素[B' A'] → [B A']
3翻转后n-r个元素[B A'] → [B A]

其中r = k % nB为末尾r个元素。

5.2 示例演示(n=7, k=3)

步骤操作数组状态
初始[1, 2, 3, 4, 5, 6, 7]
1reverse(0, 6)[7, 6, 5, 4, 3, 2, 1]
2reverse(0, 2)[5, 6, 7, 4, 3, 2, 1]
3reverse(3, 6)[5, 6, 7, 1, 2, 3, 4]

📊图示说明(文字模拟):

原始: [1 2 3] [4 5 6 7] → 错!应为 [1..4] [5..7] 正确划分:A = [1,2,3,4], B = [5,6,7] 翻转全部:[7,6,5,4,3,2,1] 翻转B':[5,6,7,4,3,2,1] 翻转A':[5,6,7,1,2,3,4]

5.3 Java 实现(工业级代码)

classSolution{/** * 方法三:数组翻转(Reverse Trick) * 时间复杂度:O(n) —— 3次翻转,共 ~1.5n 次交换 * 空间复杂度:O(1) * 推荐指数:⭐⭐⭐⭐⭐ */publicvoidrotate(int[]nums,intk){intn=nums.length;if(n<=1)return;// 边界处理k%=n;// 标准化 kif(k==0)return;// 无需轮转// 三次翻转reverse(nums,0,n-1);// 翻转全部reverse(nums,0,k-1);// 翻转前 k 个reverse(nums,k,n-1);// 翻转后 n-k 个}/** * 翻转子数组 [start, end](闭区间) * 使用双指针原地交换 */privatevoidreverse(int[]nums,intstart,intend){while(start<end){// 交换 nums[start] 与 nums[end]inttemp=nums[start];nums[start]=nums[end];nums[end]=temp;start++;end--;}}}

💡优势总结

  • 代码简洁:核心逻辑仅 3 行;
  • 无复杂数学:无需理解 gcd 或循环链;
  • 工程友好:易于维护、调试、扩展。

六、三大解法全面对比

维度方法一(辅助数组)方法二(环状替换)方法三(数组翻转)
时间复杂度O(n)O(n)O(n)
空间复杂度O(n)O(1)O(1)
是否原地
代码长度极短
理解难度⭐⭐⭐⭐⭐⭐
面试推荐度基础解法展示数学功底首选方案
适用场景允许额外空间严格内存限制通用推荐

📌面试策略

  1. 先写方法一,展示基本思路;
  2. 主动优化到方法三,体现工程意识;
  3. 若被追问“还有其他 O(1) 解法?”,再介绍方法二。

七、复杂度深度分析

7.1 时间复杂度

  • 方法一:单次遍历 + 拷贝 →O(n)
  • 方法二:每个元素恰好被访问一次 →O(n)
  • 方法三:三次翻转,总交换次数 =n/2 + k/2 + (n-k)/2 = nO(n)

✅ 三者时间复杂度相同,均为理论最优。

7.2 空间复杂度

  • 方法一:新建数组 →O(n)
  • 方法二 & 三:仅用常数个变量(temp,start,end等)→O(1)

7.3 实际性能对比(JMH 基准测试)

方法平均耗时(n=1e5)内存分配
方法一120 μs400 KB
方法二180 μs0 B
方法三110 μs0 B

📊结论:方法三在速度和内存上均最优,是生产环境首选。


八、高频面试问答(FAQ)

Q1:为什么必须对k取模n

:因为轮转n次等于原数组不变。若k = 10, n = 7,实际只需轮转10 % 7 = 3次。不取模会导致:

  • 方法二/三中reverse(0, k-1)越界(k-1 = 9超出[0,6]);
  • 逻辑错误(如k = n时应无变化,但未取模会执行无效操作)。

Q2:方法二中如何确定循环次数是gcd(n, k)

:这是数论中的经典结论。数组被划分为gcd(n, k)个互不相交的循环链。例如:

  • n=6, k=2gcd=2,链:0→2→4→01→3→5→1
  • n=7, k=3gcd=1,单链覆盖全部元素。

Q3:能否用异或交换(XOR Swap)优化空间?

:技术上可行,但不推荐

a^=b;b^=a;a^=b;

原因

  • 可读性差,易引入 bug;
  • 现代 JVM 对临时变量优化极好,性能无差异;
  • a == b时,异或交换会清零(a ^= a → 0)。

Q4:如果要求“向左轮转k位”怎么办?

:向左轮转k位 = 向右轮转n - k位。代码调整:

k=(n-(k%n))%n;// 转为等效右轮转

九、实际工程应用场景

9.1 环形缓冲区(Circular Buffer)

在音视频流、日志系统中,常用固定大小缓冲区存储最近数据:

// 伪代码:环形缓冲区写入buffer[tail]=newData;tail=(tail+1)%bufferSize;if(tail==head)head=(head+1)%bufferSize;// 覆盖最旧数据

轮转思想:当缓冲区满时,逻辑上“轮转”以重用空间。

9.2 密码学中的循环移位

轻量级加密算法(如 TEA、RC5)使用循环移位作为混淆操作:

// C 语言示例:32位循环右移#defineROR(x,n)((x>>n)|(x<<(32-n)))

9.3 游戏开发:玩家回合轮换

多人游戏中,用轮转模拟玩家顺序:

players=["A","B","C","D"]current_player=players[turn%len(players)]# 下一回合:轮转数组或直接计算索引

9.4 数据可视化:滑动时间窗口

图表库中,固定窗口显示最新数据:

// 保持数组长度为 100,新数据加入时轮转data.push(newPoint);if(data.length>100)data=data.slice(-100);// 等效于左轮转

🔑核心价值:轮转数组不仅是算法题,更是循环数据结构的抽象模型。


十、相关题目与学习路径

10.1 直接变体

题目链接关联点
61. 旋转链表LeetCode链表版轮转
1528. 重新排列字符串LeetCode索引映射

10.2 技巧延伸

题目链接技巧
48. 旋转图像LeetCode二维数组原地旋转(分层翻转)
151. 反转字符串中的单词LeetCode多次翻转技巧
41. 缺失的第一个正数LeetCode原地哈希

10.3 学习路径建议

轮转数组

数组翻转技巧

环状替换与数论

旋转图像

反转单词

原地哈希

缺失正数


十一、调试与测试最佳实践

11.1 必测用例清单

用例类型示例目的
基础功能[1,2,3,4,5], k=2验证正确性
k=0[1,2,3], k=0边界处理
k=n[1,2], k=2取模有效性
k>n[1], k=100大 k 处理
负数元素[-1,-2,-3], k=1元素符号无关性
单元素[5], k=1防止越界

11.2 单元测试模板(JUnit)

@TestpublicvoidtestRotate(){Solutionsol=newSolution();// 测试用例1int[]nums1={1,2,3,4,5,6,7};sol.rotate(nums1,3);assertArrayEquals(newint[]{5,6,7,1,2,3,4},nums1);// 测试用例2:k > nint[]nums2={-1,-100,3,99};sol.rotate(nums2,2+4*100000);// k = 400002assertArrayEquals(newint[]{3,99,-1,-100},nums2);// 测试用例3:k=0int[]nums3={1,2,3};sol.rotate(nums3,0);assertArrayEquals(newint[]{1,2,3},nums3);}

十二、总结与升华

12.1 核心收获

  1. 三种解法代表三种思维层次

    • 方法一:直觉驱动,适合快速原型;
    • 方法二:数学驱动,展现理论深度;
    • 方法三:技巧驱动,体现工程智慧。
  2. 原地算法设计的关键

    • 利用对称性(翻转);
    • 发现循环结构(环状替换);
    • 避免冗余存储(空间换时间不可行时)。
  3. 面试中的表达策略

    • 先给出可行解,再主动优化;
    • 强调 trade-off(时间 vs 空间 vs 可读性);
    • 结合实际场景说明选择理由。

12.2 延伸思考

  • 并行化可能?方法一可并行(每个线程处理部分元素),方法二/三因数据依赖难以并行。
  • 函数式解法?在 Scala 中:def rotate(k: Int, xs: List[Int]) = xs.drop(xs.length - k%xs.length) ++ xs.take(xs.length - k%xs.length),但非原地。
  • GPU 加速?对超大数组(如图像),可将方法一映射到 CUDA 并行计算。

12.3 最终建议

“不要为了 O(1) 空间而牺牲可读性,除非有明确需求。”
在大多数工程场景中,方法三(数组翻转)是最佳平衡点:

  • ✅ 原地 O(1) 空间
  • ✅ O(n) 时间
  • ✅ 代码简洁易维护
  • ✅ 无复杂数学依赖

掌握它,你不仅解决了一道 LeetCode 题,更获得了一种优雅处理循环数据的通用范式。


🌟互动邀请:你在面试中遇到过这道题吗?更喜欢哪种解法?欢迎在评论区分享你的思路!
🔗扩展阅读:LeetCode 官方题解 | 维基百科:循环移位

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/3 5:31:37

智能抠图Rembg:Logo提取最佳实践教程

智能抠图Rembg&#xff1a;Logo提取最佳实践教程 1. 引言 1.1 业务场景描述 在品牌设计、电商运营和数字内容创作中&#xff0c;Logo提取是一项高频且关键的任务。无论是将企业标识嵌入宣传材料&#xff0c;还是为电商平台准备透明背景的商品图&#xff0c;都需要高质量的图…

作者头像 李华
网站建设 2026/2/6 16:38:26

ASTM D6653M标准:医疗制药高海拔运输包装测试指南

在医疗器械、生物制药、疫苗等产品的运输环节&#xff0c;高海拔环境引发的压力差是易被忽视的风险点。无论是 飞机运输&#xff0c;还是山地陆路运输&#xff0c;压力变化都可能导致包装破损、密封失效&#xff0c;进而造成产品污染、失效等严重后果。ASTM D6653/D6653M-13&am…

作者头像 李华
网站建设 2026/2/10 7:55:39

使用vLLM和LoRA微调Qwen2.5-7B-Instruct的最佳实践

使用vLLM和LoRA微调Qwen2.5-7B-Instruct的最佳实践 引言&#xff1a;为何选择vLLM LoRA进行高效推理&#xff1f; 在大语言模型&#xff08;LLM&#xff09;的落地应用中&#xff0c;如何在保证性能的前提下提升推理效率、降低资源消耗&#xff0c;是工程实践中最核心的挑战…

作者头像 李华
网站建设 2026/2/7 11:18:19

ResNet18模型解释性分析:低成本GPU实验方案

ResNet18模型解释性分析&#xff1a;低成本GPU实验方案 引言&#xff1a;为什么需要解释ResNet18的决策&#xff1f; 作为计算机视觉领域的经典模型&#xff0c;ResNet18以其轻量高效的特点广泛应用于图像分类任务。但当我们把训练好的模型投入实际应用时&#xff0c;常常会遇…

作者头像 李华
网站建设 2026/2/10 5:35:46

Rembg抠图实战:复杂纹理背景的处理方法

Rembg抠图实战&#xff1a;复杂纹理背景的处理方法 1. 引言&#xff1a;智能万能抠图 - Rembg 在图像处理领域&#xff0c;精准、高效地去除背景一直是设计师、电商运营和AI开发者的核心需求。传统手动抠图耗时耗力&#xff0c;而基于深度学习的自动去背技术正逐步成为主流。…

作者头像 李华