【力扣100题】58.轮转数组

题目描述

给定一个整数数组 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 步: [99,-1,-100,3]
向右轮转 2 步: [3,99,-1,-100]

提示:

  • 1 <= nums.length <= 10^5
  • -2^31 <= numsi <= 2^31 - 1
  • 0 <= k <= 10^5

解题思路总览

方法 核心思想 时间复杂度 空间复杂度 备注
三次翻转 先整体翻转,再分别翻转两段 O(n) O(1) 推荐解法,最优
环状替换 每个元素放到最终位置 O(n) O(1) 需要处理 gcd
暴力法 每次轮转一个元素 O(n*k) O(1) 会超时
额外数组 借助临时数组 O(n) O(n) 最简单但空间不优

一、三次翻转法(最常用)

核心思想

通过三次反转操作,将数组向右轮转 k 位:

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

第一步:整体翻转
  reverse(nums, 0, n-1)
  [7,6,5,4,3,2,1]

第二步:翻转前 k 个元素
  reverse(nums, 0, k-1)
  [5,6,7,4,3,2,1]

第三步:翻转后 n-k 个元素
  reverse(nums, k, n-1)
  [5,6,7,1,2,3,4]

结果:[5,6,7,1,2,3,4]

图解

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

初始状态:
  [1, 2, 3, 4, 5, 6, 7]
   0   1   2   3   4   5   6

第一步:reverse(0, 6)
  [7, 6, 5, 4, 3, 2, 1]

第二步:reverse(0, 2)
  [5, 6, 7, 4, 3, 2, 1]

第三步:reverse(3, 6)
  [5, 6, 7, 1, 2, 3, 4]

输出: [5,6,7,1,2,3,4]

代码实现

cpp 复制代码
class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        k %= nums.size();           // 取模,防止 k 大于数组长度
        ranges::reverse(nums);      // 整体翻转 [1,2,3,4,5,6,7] -> [7,6,5,4,3,2,1]
        reverse(nums.begin(), nums.begin() + k);  // 翻转前 k 个 [7,6,5] -> [5,6,7]
        reverse(nums.begin() + k, nums.end());    // 翻转后 n-k 个 [4,3,2,1] -> [1,2,3,4]
    }
};

二、算法流程图

详细过程(以 nums = 1,2,3,4,5,6,7, k = 3 为例)

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

Step 1: k %= n
  n = 7, k = 3 % 7 = 3

Step 2: ranges::reverse(nums)
  翻转整个数组
  [1,2,3,4,5,6,7] -> [7,6,5,4,3,2,1]

Step 3: reverse(nums.begin(), nums.begin()+k)
  翻转前 3 个元素 [7,6,5]
  [7,6,5,4,3,2,1] -> [5,6,7,4,3,2,1]

Step 4: reverse(nums.begin()+k, nums.end())
  翻转后 4 个元素 [4,3,2,1]
  [5,6,7,4,3,2,1] -> [5,6,7,1,2,3,4]

输出: [5,6,7,1,2,3,4]

边界情况处理

复制代码
情况 1: k = 0
  k %= n = 0
  整体翻转 + 翻转前 0 个 + 翻转后 n 个 = 整体翻转再整体翻转 = 不变
  结果正确

情况 2: k = n(数组长度)
  k %= n = 0
  相当于没有轮转
  结果正确

情况 3: k > n
  例如 n=7, k=10
  k %= n = 3
  等价于轮转 3 位
  结果正确

三、逐行解析

cpp 复制代码
k %= nums.size();
  • 取模,防止 k 大于数组长度
  • 例如 n=7, k=10,k %= 7 = 3,等价于轮转 3 位
  • 当 k = 0 或 k = n 时,结果和原数组一样
cpp 复制代码
ranges::reverse(nums);
  • C++20 的 ranges::reverse,整体翻转数组
  • [1,2,3,4,5,6,7] -> [7,6,5,4,3,2,1]
  • 也可以用 reverse(nums.begin(), nums.end())
cpp 复制代码
reverse(nums.begin(), nums.begin() + k);
  • 翻转前 k 个元素
  • [7,6,5,4,3,2,1] -> [5,6,7,4,3,2,1]
  • 此时前 k 位已经在正确位置
cpp 复制代码
reverse(nums.begin() + k, nums.end());
  • 翻转后 n-k 个元素
  • [5,6,7,4,3,2,1] -> [5,6,7,1,2,3,4]
  • 完成所有调整

四、为什么三次翻转能实现轮转?

数学证明

复制代码
设数组长度为 n,轮转 k 位。

整体翻转后:原数组 [0...n-1] 变为 [n-1...0]
前 k 位翻转:[n-1...n-k] 变为 [n-k...n-1]
后 n-k 位翻转:[n-k-1...0] 变为 [n-k...n-1...0]

最终结果:[n-k...n-1] + [0...n-k-1]
        =  轮转后的正确顺序

证毕。

直观理解

复制代码
原始数组:     [1,2,3,4,5,6,7]
目标:         [5,6,7,1,2,3,4]
             = 后k位 + 前n-k位

观察:
- 后 k 位 [5,6,7] 在原数组中是前 n-k 位
- 前 n-k 位 [1,2,3,4] 在原数组中是后 n-k 位

三次翻转的作用:
1. 整体翻转 -> 把顺序反过来
2. 翻转前 k -> 把后 k 位翻到前面(但顺序反了)
3. 翻转后 n-k -> 把前 n-k 位翻到后面(顺序也反了)

两个"反了"抵消,最终得到正确顺序。

五、环状替换法

核心思想

每个元素直接放到最终位置,通过换手处理。

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

数组下标对应关系:
  原位置 0 -> 新位置 (0+k) % n = 3
  原位置 1 -> 新位置 (1+k) % n = 4
  原位置 2 -> 新位置 (5) % n = 5
  原位置 3 -> 新位置 (6) % n = 6
  原位置 4 -> 新位置 (0) % n = 0  (回到起点)
  原位置 5 -> 新位置 (1) % n = 1
  原位置 6 -> 新位置 (2) % n = 2

从位置 0 开始,顺时针走:
  0 -> 3 -> 6 -> 2 -> 5 -> 1 -> 4 -> 0
  形成一个环,共 7 个元素
cpp 复制代码
class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        k %= nums.size();
        int n = nums.size();
        int count = 0;  // 已放置的元素个数

        for (int start = 0; count < n; start++) {
            int curr = start;
            int prev = nums[start];

            do {
                int next = (curr + k) % n;
                int temp = nums[next];
                nums[next] = prev;
                prev = temp;
                curr = next;
                count++;
            } while (curr != start);

            count++;  // 补偿最后一次多减的
        }
    }
};

与三次翻转法对比

维度 三次翻转 环状替换
代码复杂度 简单,3行搞定 复杂,需要处理环
空间复杂度 O(1) O(1)
时间复杂度 O(n) O(n)
边界处理 自动处理 需要处理 gcd 防止死循环

六、复杂度分析

方法 时间复杂度 空间复杂度 备注
三次翻转 O(n) O(1) 推荐,最简单
环状替换 O(n) O(1) 需要小心处理
暴力法 O(n*k) O(1) 会超时
额外数组 O(n) O(n) 最简单但空间不优

详细分析:

复制代码
三次翻转:
  翻转 1: O(n)
  翻转 2: O(k)
  翻转 3: O(n-k)
  总计: O(n) + O(k) + O(n-k) = O(n)

空间:
  原地翻转,使用常数额外空间 O(1)

七、面试追问 FAQ

问题 回答要点
Q: 为什么三次翻转能实现轮转? 整体翻转改变顺序,前后分别翻转抵消了整体翻转带来的逆序,最终得到正确顺序
Q: k %= n 的作用是什么? 防止 k 大于数组长度,k=10, n=7 等价于 k=3,避免不必要的翻转
Q: 能否只翻转一次? 不能,一次翻转只能反转顺序,无法实现轮转
Q: 三次翻转的时间复杂度是多少? O(n),三次翻转的总时间之和为 O(n)
Q: 环状替换和三次翻转哪个更好? 三次翻转代码更简单,面试中推荐;环状替换需要处理 gcd,较复杂
Q: 如何向左轮转? 将 k 改为 n-k,或者修改翻转顺序:先翻前后两段,再整体翻转
Q: 能用队列模拟吗? 可以,每次 pop_front 再 push_back,但时间复杂度 O(n*k),会超时

八、相关题目

题目编号 题目名称 难度 核心差异
189 轮转数组 中等 基础题,向右轮转
61 旋转链表 中等 旋转链表,非数组
151 反转字符串中的单词 中等 先整体翻转再分段翻转
796 旋转字符串 简单 判断是否为旋转串
剑指 Offer 58 左旋转字符串 简单 向左轮转

九、总结

要点 内容
核心思想 三次翻转:整体 -> 前k -> 后n-k
关键操作 k %= n 防止越界
时间复杂度 O(n)(三次翻转)
空间复杂度 O(1)(原地操作)
代码行数 仅 4 行,简洁高效
注意事项 k 可能大于 n,需要取模
变形 向左轮转:将 k 改为 n-k,或修改翻转顺序

三次翻转法是轮转数组的最优解,代码简洁,面试中容易通过,建议熟练掌握。


相关推荐
先吃饱再说1 天前
判断回文字符串,从一行代码到双指针优化
算法
黄敬峰1 天前
深入理解算法核心:从递归思想、数组扁平化到快速排序
算法
得物技术1 天前
从狂野代码到按目标生产:得物推荐 AI Harness 的工程化实践|AICon 演讲整理
人工智能·算法·架构
AI小老六1 天前
SkillOpt 架构拆解:把 Skill 文本当参数,用执行轨迹训练 Agent
后端·算法·ai编程
胡萝卜术1 天前
从“分数打架”到“排名投票”:为什么你的ChatBI必须用RRF?
算法·设计模式·面试
Asize2 天前
初识DFS 与 BFS:递归、队列与图遍历
算法
罗西的思考2 天前
机器人 / 强化学习】HIL-SERL:人类在环驱动的具身智能进化框架
人工智能·算法·机器学习
美团技术团队2 天前
LongCat 开源 VitaBench 2.0:长期动态智能体基准新标杆
人工智能·算法
To_OC3 天前
LC 207 课程表:刚学图论那会儿,我连这是拓扑排序都没看出来
javascript·算法·leetcode