题目描述
给定一个整数数组 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]
提示:
1 <= nums.length <= 105-231 <= nums[i] <= 231 - 10 <= k <= 105
进阶:
- 尽可能想出更多的解决方案,至少有 三种 不同的方法可以解决这个问题。
- 你可以使用空间复杂度为
O(1)的 原地 算法解决这个问题吗?
代码实现 Solve 1 使用额外的数组
func rotate(nums []int, k int) {
n := len(nums)
numsCopy := make([]int, len(nums))
copy(numsCopy, nums)
for i := 0; i < n; i++ {
nums[(i+k)%n] = numsCopy[i]
}
}
时空复杂度
时间复杂度和空间复杂度均为 O(n)
优化代码实现 Solve
将空间复杂度优化为 O(1)。
代码实现 Solve 2 多次翻转
func rotate(nums []int, k int) {
n := len(nums)
distance := k % n
revert(nums, 0, n)
revert(nums, 0, distance)
revert(nums, distance, n)
}
func revert(nums []int, start, end int) {
for i := 0; i < (end-start)/2; i++ {
nums[start+i], nums[end-i-1] = nums[end-i-1], nums[start+i]
}
}
- 翻转整个数组
- 翻转前 k 个元素
- 翻转剩余元素
关键状态变化图
| 步骤 | 操作 | 数组状态 | 说明 |
|---|---|---|---|
| 1 | 初始状态 | [1,2,3,4,5,6,7] | 原始数组 |
| 2 | 处理 k | k = 3 % 7 = 3 | k 不需要取模,仍为 3 |
| 3 | 反转整个数组 | [7,6,5,4,3,2,1] | 将整个数组反转 |
| 4 | 反转前 k 个元素 | [5,6,7,4,3,2,1] | 将前 3 个元素反转 |
| 5 | 反转剩余元素 | [5,6,7,1,2,3,4] | 将后 4 个元素反转 |
| 6 | 返回结果 | [5,6,7,1,2,3,4] | 最终结果 |
循环替换
通过循环替换元素实现旋转,每次将一个元素放到其最终位置,并将该位置原来的元素保存用于下一次替换。
func rotate(nums []int, k int) {
n := len(nums)
processed := 0
for start := 0; processed < n; start++ {
current := start
prev := nums[start]
// 多步长交换
for {
next := (current + k) % n
tmp := nums[next]
nums[next] = prev
current = next
prev = tmp
processed++
// 回到起始位置
if current == start {
break
}
}
}
}
关键状态变化图
| 步骤 | 当前位置(current) | 当前值(prev) | 下一位置(next) | 下一值(temp) | 数组状态 | 已处理元素(count) |
|---|---|---|---|---|---|---|
| 1 | 0 | 1 | 3 | 4 | [1,2,3,1,5,6,7] | 1 |
| 2 | 3 | 4 | 6 | 7 | [1,2,3,1,5,6,4] | 2 |
| 3 | 6 | 7 | 2 | 3 | [1,2,7,1,5,6,4] | 3 |
| 4 | 2 | 3 | 5 | 6 | [1,2,7,1,5,3,4] | 4 |
| 5 | 5 | 6 | 1 | 2 | [1,6,7,1,5,3,4] | 5 |
| 6 | 1 | 2 | 4 | 5 | [1,6,7,1,2,3,4] | 6 |
| 7 | 4 | 5 | 0 | 1 | [5,6,7,1,2,3,4] | 7 |
| 8 | 0 | 1 | 3 | 1 | 回到起始位置,结束当前循环 | 7 |
考察知识点
这道题主要考察以下知识点:
-
数组操作:理解数组元素的移动和旋转。
-
原地算法:能够在 O(1) 空间复杂度下解决问题,不使用额外数组空间。
-
多种解题思路:能够提出多种不同的解决方案,体现算法设计的灵活性。
-
取模运算:处理 k 大于数组长度的情况,使用取模运算
k % n来简化问题。 -
数学思维:特别是多次反转法,利用数学性质来简化问题。
-
时间复杂度分析:
- 所有三种方法的时间复杂度都是 O(n),其中 n 是数组长度。
-
空间复杂度分析:
- 额外数组法:O(n)
- 多次反转法:O(1)
- 循环替换法:O(1)
-
边界条件处理:处理数组为空、k 为 0、k 大于数组长度的情况。
-
循环和迭代:在循环替换法中,理解如何跟踪已处理的元素和循环的终止条件。
-
原地交换:使用变量交换数组元素,如
nums[start], nums[end] = nums[end], nums[start]。
这道题展示了一个简单问题可以有多种解决方案,而且可以从空间复杂度为 O(n) 的简单解法逐步优化到空间复杂度为 O(1) 的高效解法。多次反转法是一种特别巧妙的解法,它利用了数组反转的性质来完成旋转操作。