一、前置核心知识点
1. 数组轮转基础概念
- 定义:将数组的每个元素向右(或向左)移动
k个位置,末尾元素循环回到头部。 - 示例:
[1,2,3,4,5,6,7]右移 3 位 →[5,6,7,1,2,3,4] - 关键点:
k可能大于数组长度n,需先取模k = k % len(nums)简化操作。
2. 三次反转法(最优解法)
-
核心思想:利用局部反转 + 整体反转,无需额外空间,实现 O (1) 空间复杂度。
-
操作步骤:
- 反转前 n-k 个元素
- 反转后 k 个元素
- 反转整个数组
-
原理:相对位移不变,通过三次反转实现整体右移效果。
3. Python 列表反转技巧
- 切片反转:
nums[:] = nums[::-1](原地反转) - 双指针反转:自定义
reverse函数,实现区间[i, j]的反转,效率更高且可控。
二、经典算法题:轮转数组(LeetCode 189)
题目描述
给定一个整数数组 nums,将数组中的元素向右移动 k 个位置,其中 k 是非负数。要求原地修改数组,不使用额外空间。
示例:
- 输入:
nums = [1, 2, 3, 4, 5, 6, 7],k = 3 - 输出:
[5, 6, 7, 1, 2, 3, 4]
最优解法:三次反转法
核心思路
- 处理 k 值:如果
k >= len(nums),通过取模减少移动次数。 - 局部反转:反转前
n-k个元素,反转后k个元素。 - 整体反转:反转整个数组,得到最终结果。
代码实现
from typing import List
class Solution:
def rotate(self, nums: List[int], k: int) -> None:
""" Do not return anything, modify nums in-place instead. """
n = len(nums)
k = k % n # 取模,防止k大于数组长度
if k == 0:
return # 无需反转
# 定义反转函数:双指针实现 [i, j] 区间反转
def reverse(i: int, j: int):
while i < j:
nums[i], nums[j] = nums[j], nums[i]
i += 1
j -= 1
# 1. 反转前 n-k 个元素
reverse(0, n - k - 1)
# 2. 反转后 k 个元素
reverse(n - k, n - 1)
# 3. 反转整个数组
reverse(0, n - 1)
三、关键逻辑与执行流程
1. 算法执行流程演示
以 nums = [1,2,3,4,5,6,7],k=3,n=7 为例:
- 初始:
[1, 2, 3, 4, 5, 6, 7] - 步骤 1(反转前 4 个) :
[4, 3, 2, 1, 5, 6, 7] - 步骤 2(反转后 3 个) :
[4, 3, 2, 1, 7, 6, 5] - 步骤 3(反转整体) :
[5, 6, 7, 1, 2, 3, 4]✅ 结果达成
2. 为什么要取模?
- 例如数组长度
n=5,k=7。 - 移动 7 位等价于移动
7 % 5 = 2位,减少无效操作。
3. 复杂度分析
- 时间复杂度:O (n),每个元素被反转操作访问两次。
- 空间复杂度:O (1),仅使用常数额外空间,完美符合题目要求。
四、算法对比与拓展
1. 常见解法对比
| 方法 | 时间复杂度 | 空间复杂度 | 评价 |
|---|---|---|---|
| 三次反转法 | O(n) | O(1) | ✅ 最优,推荐使用 |
| 切片法 | O(n) | O(n) | 代码简洁,但违背原地修改要求 |
| 循环替换 | O(n) | O(1) | 逻辑复杂,容易处理循环引用 |
2. 拓展应用
- 左旋转:调整三次反转的顺序,即可实现向左旋转。
- 部分旋转:只旋转数组指定区间,修改
reverse的参数范围即可。