【力扣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,或修改翻转顺序

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


相关推荐
小超人诞生日记37 分钟前
C++ 智能指针的使用及其原理详解
c++·算法
AiNightVision4 小时前
无人机夜视模块如何选择?——从参数指标到系统集成的完整选型指南
人工智能·算法·计算机视觉·车载系统·无人机·视频·智能硬件
牧羊人.3335 小时前
卷积神经网络之VGG
人工智能·深度学习·神经网络·算法·cnn
小郎君6585 小时前
Vue 多标签页的加载态设计:为什么我没有用 Suspense 实现 PageLoading
vue.js·算法
JJJennie7776 小时前
MAI Gateway私有化部署全攻略:大模型网关如何接入?附架构图
算法·gateway·ai网关·魔芋ai
東隅已逝,桑榆非晚6 小时前
算法3.双指针法-快乐数
c++·笔记·学习·算法
专注仿真6 小时前
卷积神经网络(CNN)算法详解
人工智能·python·算法·代码·大模型训练
FII工业富联科技服务6 小时前
多智能体如何进入制造现场?从业务协同到数字孪生验证
大数据·人工智能·算法·机器人·制造·数字孪生
≮傷£≯√6 小时前
httpserver_QNetWorkClient
算法
Evand J6 小时前
【PMSM例程11】永磁同步电机(PMSM)基于UKF的转速与转子位置估计。附下载链接
算法·matlab·卡尔曼滤波·永磁同步电机·ukf·pmsm·位置估计