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

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


相关推荐
youngerwang1 小时前
【从搬运工到协处理器:网卡芯片架构、算法、验证与边缘演进深度剖析】
网络·算法·架构·芯片
KaMeidebaby1 小时前
卡梅德生物技术快报|纯化重组蛋白实操详解
人工智能·python·tcp/ip·算法·机器学习
手写码匠2 小时前
从零实现 Prompt 工程引擎:结构化提示、自动优化与多轮自省体系
人工智能·深度学习·算法·aigc
无限码力3 小时前
阿里算法岗 0530笔试真题 - 多约束条件下的元素匹配统计
算法·阿里笔试真题·阿里机试真题·阿里算法岗笔试
lqqjuly3 小时前
MLA — 多头潜在注意力深度解析
深度学习·神经网络·算法
吴可可1233 小时前
SolidWorks草图转三维DWG技巧
算法
redaijufeng4 小时前
C++雾中风景7:闭包
c++·算法·风景
小欣加油4 小时前
leetcode287寻找重复数
数据结构·c++·算法·leetcode
尽兴-5 小时前
2.1 向量基础:Embedding、余弦相似度、欧氏距离、向量检索
算法·embedding·欧氏距离·向量检索·余弦相似度
Black蜡笔小新5 小时前
自动化AI算法训练服务器DLTM训推一体工作站赋能多行业智能化升级
人工智能·算法·自动化