主题
思路讲解
看示例 1,我们要把
把
反转后数组变成

这用到了「负负得正」的想法:把一个子数组反转两次,子数组不变。
在第一次反转中,我们不仅反转了子数组
算法
这里假设
,对于 的情况,可以转换成 的情况(证明见后文)。
设
题目要求把
- 把
反转,我们得到了 ,其中 表示数组 反转后的结果。在上例中, 。 - 单独反转
,因为一个数组反转两次是不变的,所以 ,我们得到了 。 - 单独反转
,得到 。 - 现在数组变成
。在上例中, ,这正是我们想要的结果。
python
# 注:请勿使用切片,会产生额外空间
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
def reverse(i: int, j: int) -> None:
while i < j:
nums[i], nums[j] = nums[j], nums[i]
i += 1
j -= 1
n = len(nums)
k %= n # 轮转 k 次等同于轮转 k % n 次
reverse(0, n - 1)
reverse(0, k - 1)
reverse(k, n - 1)cpp
// C++ 版待补充cpp
class Solution {
public:
void rotate(vector<int>& nums, int k) {
k %= nums.size(); // 轮转 k 次等同于轮转 k % n 次
ranges::reverse(nums);
reverse(nums.begin(), nums.begin() + k);
reverse(nums.begin() + k, nums.end());
}
};cpp
class Solution {
public:
void rotate(vector<int>& nums, int k) {
auto reverse = [&](int i, int j) {
while (i < j) {
swap(nums[i++], nums[j--]);
}
};
int n = nums.size();
k %= n; // 轮转 k 次等同于轮转 k % n 次
reverse(0, n - 1);
reverse(0, k - 1);
reverse(k, n - 1);
}
};复杂度分析
- 时间复杂度:
,其中 是 的长度。 - 空间复杂度:
。
附:正确性证明
向右轮转
下面证明「三次反转法」的正确性。
假设
- 如果
,那么第一次反转(整个数组的反转)会把下标 的元素交换到下标 上(注意 ,在第二次反转的范围中),第二次反转会把下标 的元素交换到下标 上。根据 可得 ,所以 ,符合题目要求。 - 如果
,那么第一次反转(整个数组的反转)会把下标 的元素交换到下标 上(注意 ,在第三次反转的范围中),第三次反转会把下标 的元素交换到下标 上(注*)。根据 可得 ,所以 ,符合题目要求。
对于
综上所述,对于任意非负整数
注:对于下标区间
变形题
- 改成向左轮转
个位置。 - 如果
是个二维数组呢?见 1260. 二维网格迁移。
分类题单
- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
- 单调栈(基础/矩形面积/贡献法/最小字典序)
- 网格图(DFS/BFS/综合应用)
- 位运算(基础/性质/拆位/试填/恒等式/思维)
- 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
- 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
- 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
欢迎关注 B站@灵茶山艾府