LeetCode 189. 轮转数组(三次反转法)
题目描述
给定一个整数数组 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]
解法思路:三次反转法
核心思想
向右轮转 k 位,本质上是将数组的后 k 个元素移动到数组前面,同时保持顺序不变。
我们可以通过三次数组反转来实现原地操作,无需额外空间。
算法步骤
- 取模 :
k = k % n,因为轮转n次后数组恢复原样,只需处理有效偏移。 - 反转整个数组 :将
nums完全反转。 - 反转前
k个元素 :将前k个元素反转,它们原本是原数组的后k个(倒序),反转后恢复正确顺序,并移到开头。 - 反转后
n-k个元素:将剩余元素反转,恢复其原本顺序,放在开头之后。
数学推导
设原数组为 [a₁, a₂, ..., aₙ],向右轮转 k 位后目标为 [aₙ₋ₖ₊₁, ..., aₙ, a₁, ..., aₙ₋ₖ]。
- 第一次反转:
[a₁, ..., aₙ]→[aₙ, aₙ₋₁, ..., a₁] - 第二次反转(前 k 个):前 k 个为
[aₙ, aₙ₋₁, ..., aₙ₋ₖ₊₁]→ 反转得[aₙ₋ₖ₊₁, ..., aₙ](即为目标的前半部分) - 第三次反转(后 n-k 个):后 n-k 个为
[aₙ₋ₖ, ..., a₁]→ 反转得[a₁, ..., aₙ₋ₖ](即为目标的后半部分)
最终得到正确结果。
代码实现(C++)
cpp
class Solution {
public:
void rotate(vector<int>& nums, int k) {
int n = nums.size();
k %= n; // 处理无效轮转
reverse(nums.begin(), nums.end()); // 反转整个数组
reverse(nums.begin(), nums.begin() + k); // 反转前 k 个
reverse(nums.begin() + k, nums.end()); // 反转后 n-k 个
}
};
代码说明
reverse是 C++ STL 中的双向迭代器反转函数,时间复杂度 O(n)。- 边界情况:
k = 0或k为n的倍数时,k取模后为 0,此时begin() + 0与begin()相同,第三次反转整个数组(相当于反转两次),数组不变,符合预期。
复杂度分析
- 时间复杂度:O(n),其中 n 是数组长度。三次反转总共遍历数组常数次。
- 空间复杂度:O(1),只使用了常数个额外变量,原地修改。
举例演示
输入 :nums = [1, 2, 3, 4, 5, 6, 7],k = 3
| 步骤 | 操作 | 数组状态 |
|---|---|---|
| 初始 | - | [1, 2, 3, 4, 5, 6, 7] |
| ① | 反转整个数组 | [7, 6, 5, 4, 3, 2, 1] |
| ② | 反转前 3 个元素(索引 0~2) | [5, 6, 7, 4, 3, 2, 1] |
| ③ | 反转后 4 个元素(索引 3~6) | [5, 6, 7, 1, 2, 3, 4] |
最终结果 [5, 6, 7, 1, 2, 3, 4] 正确。
边界情况测试
- k = 0:数组不变。
- k = n:取模后为 0,数组不变。
- k > n :如
k = 10, n = 7,取模后k = 3,等价于轮转 3 位,正确。 - 空数组 :
n = 0时需特殊处理,但题目一般不会给空数组,若处理可提前判断。
总结
- 三次反转法是一种巧妙的原地算法,利用反转的逆序特性实现区间交换。
- 注意取模操作避免冗余轮转,并确保反转区间合法。