有些人可能觉得很奇怪,一道数组轮转题怎么会被反复拿出来讲十几年。但如果你把力扣189和2010年408那道代码题放在一起看,会发现它们几乎就是同一道题穿了不同的马甲。轮转数组这个操作,表面上只是把元素挪个位置,实际上是检验一个人对数组下标、取模运算、还有原地操作理解程度的试金石。
我不止一次在评论区看到有人说“这题用临时数组一下就写完了,为什么要整什么反转法”。确实,工作里写业务代码你完全可以new一个新数组,没人会拦你。但408不会惯着你,LeetCode的进阶要求也不会惯着你。当空间复杂度被限制成O(1),当考场上要求你写时间上尽可能高效的原地算法时,三次反转和环状替换就成了两种绕不开的标准兵器。
这篇文章就专门来拆这道题,覆盖两种主流原地解法、暴力法和额外数组法的踩坑体验,再回头对照一下2010年那道408真题,把左右移位的关系彻底捋清楚。无论你是刷力扣找手感,还是准备408正在背代码,看到最后应该都能有收获。
1. 为什么一道数组轮转题能穿越十来年
先说一个可能出乎你意料的事实:力扣第189题的英文名是Rotate Array,国内翻译成“轮转数组”,而2010年408那道真题描述里写的则是“循环左移若干位置”。一个向右,一个向左,但底层逻辑一模一样。
1.1 力扣189到底在考什么
力扣的原题描述很短:“给定一个整数数组nums,将数组中的元素向右轮转k个位置,其中k是非负数。”
题目举的例子是nums = [1,2,3,4,5,6,7], k = 3,输出[5,6,7,1,2,3,4]。
这个“向右轮转”的动作,你可以把它想象成把数组排成一个环,所有元素整体往右挪动k格,超出数组末尾的元素绕回开头。如果你选择粗暴地循环k次、每次把整个数组右移一位,那这道题就退化成O(nk)的级别。当n和k都很大的时候,基本只能等着超时。
所以它真正想考的是两件事:第一,你能不能找到一次移动就位的规律;第二,你能不能把额外空间压下去。LeetCode还专门留了一句进阶要求:尝试使用空间复杂度为O(1)的原地算法解决这个问题。
1.2 和408真题之间只差一个方向
2010年408那道题的过程大致是这样的:把n个整数保存在一维数组中,设计一个时间和空间两方面都尽可能高效的算法,把序列循环左移p个位置。题中给了样例,原序列1到9,左移3位后变成4到9然后接上1到3。
把这两道题放一起看,方向相反不影响本质。所谓左移p位,其实就等价于右移n-p位。比如n=9,p=3,左移3位等价于右移6位。你写出右轮转函数,把参数传成n-p,左移立刻完成。
我后来在复盘408真题解法时,最大的感触就是:如果一个人能先把力扣189想透,那408这道大题几乎等于送分。反过来,如果你是按考研的要求把189吃透,去面试的时候把它现场写出来,也完全是稳稳的加分项。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 先别急着上高级写法,暴力法到底慢在哪
很多LeetCode题解上来就甩反转法,看起来确实酷,但一个对轮转操作没有直观感受的人,直接背反转法很容易出事故。我建议新手先把暴力法和额外数组法写一遍,踩一遍坑,才能真正理解为什么后面两种写法更优。
2.1 暴力移动解法与它的复杂度陷阱
最朴素的写法是这样:每次把数组最后一个元素存下来,然后把前面所有元素往后挪一位,最后把存下来的那个元素放到数组开头。这样就完成了一次轮转。外面再套一个for循环让它转k次。
python复制def rotate_violent(nums, k):
n = len(nums)
k %= n
for _ in range(k):
last = nums[-1]
for i in range(n - 1, 0, -1):
nums[i] = nums[i - 1]
nums[0] = last
这段代码的思路很直白,没有任何技巧,直接模拟元素的移动过程。但注意看时间开销,外层执行k次,内层每轮要移动n-1个元素,总次数是O(nk)。假设n是10万,k也是10万,那你就要执行百亿次赋值。这个量级放在任何一种OJ上都不可能通过。
还有一个隐藏的小坑:不先执行k %= n的话,k一但大于n,代码里就会多出一大堆根本不需要的重复轮转。比如n=5,k=12,向左或向右轮转12次的结果,其实和第5个元素?不,因为轮转n次等于回原位,先对k取模成2,得到的结果和12次完全一样。我在刚开始刷题时总会忘这一步,所以还是把它写在前面了。
2.2 额外数组解法,空间换时间一时爽
暴力法的优化方向很自然。既然每个元素的落点可以直接被算出来,那我干脆开一个新数组,把nums[i]放到新位置(i + k)
