🔄 题目链接:leetcode.cn/problems/ro…
🔍 难度:中等 | 🏷️ 标签:数组、双指针、数学、原地操作
⏱️ 目标时间复杂度:O(n)
💾 空间复杂度:O(1)
🧠 题目分析
给定一个整数数组 nums,要求将数组中的元素 向右轮转 k 个位置。注意:
- 轮转是“循环”的:超出末尾的元素会回到开头;
k可能大于数组长度,因此实际有效轮转次数为k % n;- 题目要求 原地修改(in-place),即不能使用额外 O(n) 空间(除非作为进阶对比);
- 进阶明确指出:至少要有三种解法,且必须掌握 O(1) 空间的原地算法。
这道题看似简单,但考察了对 数组索引映射、模运算、循环节、原地操作技巧 的综合理解,是面试高频题!
🔑 核心算法及代码讲解:数组翻转法(推荐!)
这是最优雅、最易实现、且满足 O(1) 空间要求的解法。其核心思想基于一个观察:
向右轮转
k位,等价于:
- 将整个数组反转;
- 将前
k个元素反转;- 将后
n - k个元素反转。
✅ 为什么这样是对的?
假设原数组为:
[1, 2, 3, 4, 5, 6, 7],k = 3
- 轮转后应为:
[5, 6, 7, 1, 2, 3, 4] - 注意:最后 3 个元素
[5,6,7]跑到了前面
如果我们先整体翻转:
→ [7, 6, 5, 4, 3, 2, 1]
此时,原来的“尾巴” [5,6,7] 变成了“头部”,但顺序反了;
原来的“头部” [1,2,3,4] 变成了“尾巴”,也反了。
于是我们分别翻转前 k=3 个和后 4 个:
- 翻转
[7,6,5]→[5,6,7] - 翻转
[4,3,2,1]→[1,2,3,4]
最终得到:[5,6,7,1,2,3,4] ✅
📜 代码实现(带详细行注释)
// 翻转子数组 [start, end](闭区间)
void reverse(vector<int>& nums, int start, int end) {
while (start < end) {
swap(nums[start], nums[end]); // 交换首尾元素
start++; // 左指针右移
end--; // 右指针左移
}
}
// 主函数:原地轮转数组
void rotate(vector<int>& nums, int k) {
int n = nums.size();
k %= n; // 处理 k >= n 的情况,取模得有效轮转数
reverse(nums, 0, n - 1); // 第一步:整体翻转
reverse(nums, 0, k - 1); // 第二步:翻转前 k 个元素
reverse(nums, k, n - 1); // 第三步:翻转后 n-k 个元素
}
💡 关键点:
k %= n是必须的!否则当k > n时,k - 1可能越界(如k = 10,n = 7→k = 3)。
🧩 解题思路(分步骤)
- 预处理 k:由于轮转
n次等于没动,所以只需轮转k % n次; - 整体翻转:使“要移到前面的尾部元素”出现在数组开头,但顺序颠倒;
- 局部翻转:
- 翻转前
k个 → 恢复正确顺序; - 翻转后
n - k个 → 恢复剩余部分顺序;
- 翻转前
- 完成原地轮转,无需额外空间。
📊 算法分析
| 方法 | 时间复杂度 | 空间复杂度 | 是否原地 | 面试推荐度 |
|---|---|---|---|---|
| 额外数组 | O(n) | O(n) | ❌ | ⭐⭐ |
| 环状替换 | O(n) | O(1) | ✅ | ⭐⭐⭐ |
| 数组翻转 | O(n) | O(1) | ✅ | ⭐⭐⭐⭐⭐ |
- 时间:三次翻转,每个元素最多被交换两次 →
O(2n) = O(n) - 空间:仅用几个变量 →
O(1) - 稳定性:不改变相对顺序(因为只是块移动)
- 鲁棒性:自动处理
k = 0、k = n、k > n等边界情况
🎯 面试加分项:能清晰解释“为什么三次翻转能得到结果”,并画出示意图。
💡 其他解法简述(拓展视野)
方法一:额外数组(简单直观)
- 新建数组
newArr,newArr[(i + k) % n] = nums[i] - 缺点:空间 O(n),不符合进阶要求
- 适合快速 AC 或作为验证手段
方法二:环状替换(数学思维强)
- 利用 最大公约数 gcd(n, k) 确定需要多少个“环”
- 每个环内通过
temp临时变量完成轮换 - 难点:理解为何环的数量是
gcd(n, k) - 优点:真正“逐元素移动”,无冗余操作
- 缺点:代码易错,边界复杂
📌 面试建议:优先讲 翻转法,因其简洁、安全、易写;若被追问“还有别的方法吗?”,再提环状替换展示深度。
💻 完整可运行代码(含测试)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
// 翻转子数组 [start, end]
void reverse(vector<int>& nums, int start, int end) {
while (start < end) {
swap(nums[start], nums[end]);
start++;
end--;
}
}
// 原地轮转数组
void rotate(vector<int>& nums, int k) {
int n = nums.size();
k %= n;
reverse(nums, 0, n - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, n - 1);
}
// 测试
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
// 测试用例 1
vector<int> nums1 = {1,2,3,4,5,6,7};
rotate(nums1, 3);
for (int x : nums1) cout << x << " "; // 输出: 5 6 7 1 2 3 4
cout << "\n";
// 测试用例 2
vector<int> nums2 = {-1,-100,3,99};
rotate(nums2, 2);
for (int x : nums2) cout << x << " "; // 输出: 3 99 -1 -100
cout << "\n";
// 边界测试:k = 0
vector<int> nums3 = {1,2};
rotate(nums3, 0);
for (int x : nums3) cout << x << " "; // 输出: 1 2
cout << "\n";
// 边界测试:k > n
vector<int> nums4 = {1,2,3};
rotate(nums4, 5); // 5 % 3 = 2
for (int x : nums4) cout << x << " "; // 输出: 2 3 1
cout << "\n";
return 0;
}
🌟 本期完结,下期见!🔥
👉 点赞收藏加关注,新文更新不迷路。关注专栏【算法】LeetCode Hot100刷题日记,持续为你拆解每一道热题的底层逻辑与面试技巧!
💬 欢迎留言交流你的解法或疑问!一起进步,冲向 Offer!💪
📣 下一期预告:LeetCode 热题 100 第16题 ——238. 除自身以外数组的乘积 —— 前缀积与后缀积(空间优化)
📌 题目链接:leetcode.cn/problems/pr…
🔍 难度:中等 | 🏷️ 标签:数组、前缀积、后缀积、双指针、原地操作
⏱️ 目标时间复杂度:O(n)
💾 空间复杂度:O(1) (不计入输出数组)
📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!