跳到主要内容

189. 轮转数组

题目描述

给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。

要求:使用 O(1) 额外空间(原地修改)。

示例

示例 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 个元素会移动到数组前面
  • 前面 n-k 个元素会移动到数组后面

例如 [1,2,3,4,5,6,7] 轮转 3 位后变成 [5,6,7,1,2,3,4]

第二步:暴力解法

每次将数组最后一个元素取出,所有元素右移一位,再将取出的元素放到首位。重复 k 次。

def rotate(self, nums: List[int], k: int) -> None:
n = len(nums)
k %= n
for _ in range(k):
last = nums[-1]
for i in range(n - 1, 0, -1):
nums[i] = nums[i - 1]
nums[0] = last

为什么不够好:时间复杂度 O(n×k),当 k 接近 n 时达到 O(n²)。

第三步:优化解法 - 使用额外数组

创建一个新数组,直接按轮转后的位置放置元素。

def rotate(self, nums: List[int], k: int) -> None:
n = len(nums)
k %= n
temp = nums[:] # 复制原数组
for i in range(n):
nums[(i + k) % n] = temp[i]

分析:时间复杂度 O(n),但需要 O(n) 额外空间。

第四步:最优解法 - 三次翻转法

这是本题最优雅的解法,通过三次翻转实现原地轮转:

  1. 翻转整个数组 [0, n-1]
  2. 翻转前 k 个元素 [0, k-1]
  3. 翻转剩余元素 [k, n-1]

为什么这样有效?

假设原数组为 [1,2,3,4,5,6,7],k=3:

步骤操作结果说明
初始-[1,2,3,4,5,6,7]原数组
第1步整体翻转[7,6,5,4,3,2,1]顺序完全颠倒
第2步翻转前3个[5,6,7,4,3,2,1]前3个恢复正确顺序
第3步翻转后4个[5,6,7,1,2,3,4]后4个恢复正确顺序

数学原理:翻转操作具有对称性,三次特定的翻转组合恰好能产生轮转效果。


完整代码实现

from typing import List

class Solution:
"""
轮转数组 - 数组翻转法

核心思路:
通过三次翻转实现数组轮转:
1. 先翻转整个数组 [0, n-1]
2. 再翻转前 k 个元素 [0, k-1]
3. 最后翻转剩余元素 [k, n-1]

时间复杂度:O(n)
空间复杂度:O(1)
"""

def rotate(self, nums: List[int], k: int) -> None:
"""Do not return anything, modify nums in-place instead."""
n = len(nums)
k %= n # 处理 k > n 的情况

# 三次翻转实现轮转效果
self.reverse(nums, 0, n - 1) # 第一步:翻转整个数组
self.reverse(nums, 0, k - 1) # 第二步:翻转前 k 个元素
self.reverse(nums, k, n - 1) # 第三步:翻转剩余元素

def reverse(self, nums: List[int], left: int, right: int) -> None:
"""辅助函数:反转数组区间 [left, right]"""
while left < right:
nums[left], nums[right] = nums[right], nums[left]
left += 1
right -= 1

示例推演

nums = [1,2,3,4,5,6,7], k = 3 为例:

第一步:k %= n

  • k = 3 % 7 = 3

第二步:翻转整个数组 [0, 6]

  • 初始: [1,2,3,4,5,6,7]
  • 交换 1↔7: [7,2,3,4,5,6,1]
  • 交换 2↔6: [7,6,3,4,5,2,1]
  • 交换 3↔5: [7,6,5,4,3,2,1]
  • 中间元素 4 无需交换
  • 结果: [7,6,5,4,3,2,1]

第三步:翻转前 k=3 个元素 [0, 2]

  • 初始: [7,6,5,4,3,2,1]
  • 交换 7↔5: [5,6,7,4,3,2,1]
  • 结果: [5,6,7,4,3,2,1]

第四步:翻转剩余元素 [3, 6]

  • 初始: [5,6,7,4,3,2,1]
  • 交换 4↔1: [5,6,7,1,3,2,4]
  • 交换 3↔2: [5,6,7,1,2,3,4]
  • 结果: [5,6,7,1,2,3,4]

复杂度分析

解法时间复杂度空间复杂度说明
暴力O(n×k)O(1)每次移动一位,重复k次
优化O(n)O(n)使用额外数组存储
最优(三次翻转)O(n)O(1)每个元素被访问常数次,原地操作

易错点总结

1. k 可能大于数组长度

错误:直接使用 k 作为翻转边界。

正确k %= n,因为轮转 n 次等于原数组。

# 错误
k = k # 当 k > n 时会出错

# 正确
k %= n # 处理 k > n 的情况

2. 翻转边界处理

翻转区间 [left, right] 是闭区间,双指针相遇时停止。

while left < right:  # 不是 <=
nums[left], nums[right] = nums[right], nums[left]
left += 1
right -= 1

3. 三次翻转的顺序

必须是:整体 → 前k个 → 剩余。顺序错误会导致错误结果。


扩展思考

1. 向左轮转怎么办?

向左轮转 k 位等价于向右轮转 n-k 位:

def rotate_left(nums, k):
n = len(nums)
rotate_right(nums, n - k % n)

2. 相关题目

3. 算法本质

三次翻转法的本质是置换群的应用。数组轮转可以看作是一个循环置换,而翻转操作可以分解和重组这个置换。


相关题目

加载评论中...