LeetCode 189 轮转数组:三次反转为什么能实现右旋?

前言

第一次看到"三次反转就能完成数组轮转"时,我的第一反应其实是:这更像一个需要背下来的技巧。

先反转整个数组,再反转前 k 个元素,最后反转剩余元素------步骤确实不多,但如果只记住这三行代码,过几天很容易把反转顺序或者区间边界写错。

后来把数组拆成两部分重新看了一遍,才发现这并不是什么魔法。所谓向右轮转,本质上就是把数组末尾的一段搬到开头,同时保持前后两段内部的顺序不变。三次反转正好完成了这件事。

本文就从这个角度,讲清楚 LeetCode 189"轮转数组"的原地反转解法。

一、题目说明

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

例如:

复制代码
输入:nums = [1,2,3,4,5,6,7], k = 3
输出:[5,6,7,1,2,3,4]

向右轮转一次,数组最后一个元素会移动到最前面:

复制代码
[7,1,2,3,4,5,6]

继续轮转两次,最终得到:

复制代码
[5,6,7,1,2,3,4]

题目要求我们直接修改原数组,不需要返回新的数组。

二、最容易想到的方法:一步一步移动

最直观的做法,是每轮都保存数组最后一个元素,再把其他元素整体向后移动一位,最后将保存的元素放到数组开头。重复 k 次,就可以得到答案。

这种方法当然能做出来,但存在一个明显问题:每轮都要移动接近 n 个元素,一共执行 k 轮,时间复杂度会达到 O(n × k)。

如果数组很长,而且 k 也很大,很多重复移动其实没有必要。我们真正想做的,不是模拟每一步轮转,而是一次性把应该出现在前面的那部分元素放过去。

三、换个角度:右旋其实是在交换两段

仍然看下面这个数组:

复制代码
[1,2,3,4,5,6,7],k = 3

向右轮转 3 位后,数组末尾的 3 个元素会移动到开头:

复制代码
[5,6,7,1,2,3,4]

我们可以把原数组分为两段:

复制代码
A = [1,2,3,4]
B = [5,6,7]

原数组是:

复制代码
A + B

轮转后的数组则是:

复制代码
B + A

所以问题变成了:如何在不使用额外数组的情况下,把 A + B 变成 B + A?

答案就是三次反转。

四、三次反转的完整过程

第一步:反转整个数组

复制代码
[1,2,3,4,5,6,7]
            ↓
[7,6,5,4,3,2,1]

如果用前面的分组表示,A + B 整体反转后会变成:

复制代码
reverse(B) + reverse(A)

此时 B 已经被移动到了前面,但 A、B 两段内部的顺序都反了。

第二步:反转前 k 个元素

当前数组为:

复制代码
[7,6,5,4,3,2,1]

反转前 3 个元素:

复制代码
[5,6,7,4,3,2,1]

这一步将 reverse(B) 恢复成了原来的 B:

复制代码
B + reverse(A)

第三步:反转后 n-k 个元素

继续反转下标 k 到 n - 1 之间的元素:

复制代码
[5,6,7,4,3,2,1]
            ↓
[5,6,7,1,2,3,4]

reverse(A) 也恢复成了原来的 A,最终得到:

复制代码
B + A

这正是向右轮转后的结果。

所以,三次反转并不是碰巧有效,它对应着一个很清楚的过程:先借助整体反转交换两段的位置,再分别修复两段内部的顺序。

五、为什么一定要对 k 取模

代码中的这一行很容易被忽略:

复制代码
k = k % len;

假设数组长度为 7,向右轮转 7 次以后,数组会回到原来的状态。因此:

复制代码
轮转 8 次 = 轮转 1 次
轮转 10 次 = 轮转 3 次

真正有效的轮转次数只有 k % len。

如果不取模,当 k 大于数组长度时,k - 1 就可能超出数组下标范围。例如,数组长度为 4、k = 6,实际只需要轮转:

复制代码
6 % 4 = 2

取模既消除了重复操作,也保证了后续反转区间合法。

六、Java 完整代码

复制代码
class Solution {
    public void rotate(int[] nums, int k) {
        int len = nums.length;

        // 对 k 取模,避免 k 大于数组长度
        k = k % len;

        // 第一次:反转整个数组
        reverse(nums, 0, len - 1);

        // 第二次:反转前 k 个元素
        reverse(nums, 0, k - 1);

        // 第三次:反转剩余的 n-k 个元素
        reverse(nums, k, len - 1);
    }

    private void reverse(int[] nums, int left, int right) {
        while (left < right) {
            int temp = nums[left];
            nums[left] = nums[right];
            nums[right] = temp;

            left++;
            right--;
        }
    }
}

七、复杂度分析

三次反转分别处理 n、k 和 n-k 个元素。虽然代码中调用了三次 reverse,但总操作次数仍然与数组长度成正比,因此:

  • 时间复杂度:O(n);

  • 空间复杂度:O(1)。

整个过程直接在原数组上交换元素,没有创建长度与输入规模相关的辅助数组,这也是该解法最有价值的地方。

相关推荐
jidaowansui21 分钟前
B3643 图的存储———>数组模拟邻接矩阵
算法
y1su32 分钟前
Leetcode 二分模板
java·数据结构·算法·leetcode·排序算法
bksczm34 分钟前
数据结构的大纲概括
数据结构
youshi666839 分钟前
平衡车——PID算法优化
stm32·单片机·嵌入式硬件·算法·pid·平衡车
千千寰宇1 小时前
[数据库系统] 数据库系统的设计原理研究
数据结构·数据库·database-opengemini/influxdb·database-dament/达梦
聪明蛋子哟2 小时前
不仅仅是向量检索:结合知识图谱与Tool Calling的混合增强生成(Hybrid RAG)方案
人工智能·算法·知识图谱
Pniubi2 小时前
力扣55跳跃游戏(贪心)
算法·leetcode·游戏
jyyyx的算法博客2 小时前
传球游戏【思维】
算法
FL16238631292 小时前
关于2026年南昌大学811信号与系统最后一题电路系统解题思路
算法
殷色玫瑰2 小时前
C++ STL:map 与 set 详解——从底层红黑树到实际应用
c语言·数据结构·c++·算法·visualstudio·rpc