Hot 100 --- 轮转数组
本文概览:本文以LeetCode经典题目"轮转数组"为例,从暴力解法入手,逐步优化到 O(n) 额外空间的解法,再通过三次翻转法实现 O(1) 空间复杂度的原地修改,系统讲解如何用红黑笔的比喻理解三次翻转的巧妙之处
一、题目

二、题目分析
给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置
目标:原地修改数组,实现右轮转 k 位
从过程看轮转
最直观的理解是"过程视角":每次把数组所有元素右移一位,重复 k 次。但这种方式时间复杂度极高
从结果看轮转
如果只看结果而不看过程,轮转其实就是把后面长度为 k 的子数组和前面长度为 n-k 的子数组交换位置。例如 nums = [1,2,3,4,5,6,7], k = 3,轮转后就是 [5,6,7,1,2,3,4],本质上是把 [5,6,7] 放到了开头,[1,2,3,4] 放到了后面
所以下面的解法都是基于"把后面长度为 k 的子数组和前面长度为 n-k 的子数组交换位置"这个视角来的
思路概览
Java实现代码如下
1 | public void rotate(int[] nums, int k) { |
思路简要说明
反转整个数组:将数组完全翻转
反转前 k 个元素:将翻转后位于前 k 位的元素(即原来的后 k 个元素)再翻转回来,恢复正确顺序
反转剩余元素:将翻转后位于后 n-k 位的元素(即原来的前 n-k 个元素)再翻转回来,恢复正确顺序
三、思路详解
暴力解法入手
最直接的暴力做法是:每次将数组所有元素右移一位,重复 k 次。每次右移需要遍历整个数组,共 k 次
- 时间复杂度:O(n × k),当 k 很大时效率极低
- 核心瓶颈:每次只移动一位,做了大量重复的挪动操作
- 关键思考:能否一次性完成交换,而不是逐位挪动?
O(n) 额外空间解法
既然轮转的本质是把后面长度为 k 的子数组放到开头,前面长度为 n-k 的子数组放到后面,那最简单的做法就是新开一个数组,直接把后 k 个元素放到新数组的开头,前 n-k 个元素放到新数组的后面,最后把新数组复制回原数组
1 | public void rotate(int[] nums, int k) { |
- 时间复杂度:O(n),只需遍历一次
- 空间复杂度:O(n),需要额外开一个长度为 n 的数组
- 核心瓶颈:数组比较大时,额外开辟一个同等大小的数组空间开销非常大
- 关键思考:能否不开新数组,原地完成交换?
三次翻转法(O(1) 空间)
思路分析
不开新数组,直接把后面 k 个元素和前面 n-k 个元素交换位置,在代码层面需要一个中间变量来记录数组,无法实现原地反转
但有一个小巧思——三次翻转法
我们用红笔和黑笔来理解:假设有一排笔排成一排,笔头朝右,黑笔代表后面长度为 k 的子数组,红笔代表前面长度为 n-k 的子数组
1 | 轮转前:→→→→ →→→ (红红红红 黑黑黑,笔头都朝右) |
我们要的就是黑笔在前面、红笔在后面,且笔头都朝右。最直接的想法就是把红笔和黑笔交换顺序,但这需要额外空间
三次翻转的巧妙之处:
第一步:反转整个数组
1 | 反转前:→→→→ →→→ (红红红红 黑黑黑) |
反转后,黑笔已经到了前面,红笔已经到了后面,位置已经符合要求了!但是有个问题:所有笔的笔头朝向都反了——原来朝右,现在朝左
第二步:反转前 k 个元素(把黑笔的笔头翻回去)
1 | 反转前:←←← ←←←← |
第三步:反转后 n-k 个元素(把红笔的笔头翻回去)
1 | 反转前:→→→ ←←←← |
三次翻转后,黑笔在前面且笔头朝右,红笔在后面且笔头朝右,轮转完成!
为什么三次翻转等价于轮转?
从数学角度理解:
- 原数组:
[A | B],其中 A 是前 n-k 个元素,B 是后 k 个元素 - 目标:
[B | A] - 反转整个数组:
[A' | B'](A' 是 A 的反转,B' 是 B 的反转) - 反转前 k 个(即 B'):
[B | A'] - 反转后 n-k 个(即 A'):
[B | A]
每一步反转都是原地的,只需要双指针交换,空间复杂度 O(1)
举例说明
以 nums = [1,2,3,4,5,6,7], k = 3 为例
第一步:反转整个数组
1 | [1,2,3,4,5,6,7] → [7,6,5,4,3,2,1] |
第二步:反转前 k=3 个元素
1 | [7,6,5,4,3,2,1] → [5,6,7,4,3,2,1] |
第三步:反转后 n-k=4 个元素
1 | [5,6,7,4,3,2,1] → [5,6,7,1,2,3,4] |
最终结果为 [5,6,7,1,2,3,4],轮转正确
- 时间复杂度:O(n),三次反转各遍历一部分,总共遍历 2n 次
- 空间复杂度:O(1),只用了常数个临时变量


