1. 概述
在实际开发中,数组的循环移位是一个常见需求,比如在滑动窗口、队列操作或数据重排场景中都会遇到。所谓“循环右移一位”,就是将数组最后一个元素移到首位,其余元素整体后移一位。
本文将系统介绍在 Kotlin 中实现数组循环右移一位的几种方法。目标是帮助你在不同场景下选择最合适的方案,避免踩坑。✅
示例:
[1, 2, 3, 4, 5]
→[5, 1, 2, 3, 4]
2. 手动循环法(原地操作)
这是最基础也是性能最高的方式,适合对时间复杂度敏感的场景。核心思路是:
- 用临时变量保存最后一个元素
- 从后往前遍历,依次将前一个元素赋值给当前元素
- 最后把临时变量赋给首元素
fun rotateArrayByOneProgramaticApproach(arr: IntArray) {
val temp = arr.last()
for (i in arr.size - 1 downTo 1) {
arr[i] = arr[i - 1]
}
arr[0] = temp
}
✅ 优点:原地操作,空间复杂度 O(1),无额外对象创建
⚠️ 注意:修改的是原数组,调用前需确认是否允许
测试验证:
@Test
fun `cyclically rotate an array by one position using programmatic approach`() {
val arr = intArrayOf(1, 2, 3, 4, 5)
rotateArrayByOneProgramaticApproach(arr)
assertArrayEquals(intArrayOf(5, 1, 2, 3, 4), arr)
}
3. 使用 copyInto() 方法
Kotlin 提供了 copyInto()
方法用于高效复制数组片段。我们可以利用它来避免手动写循环。
关键参数说明(按顺序):
- 目标数组
- 目标数组的起始偏移量
- 源数组的起始索引
- 源数组的结束索引(不包含)
实现方式:
- 将前 n-1 个元素复制到新数组的第 1 位开始的位置
- 将原数组最后一个元素放入新数组第 0 位
- 将新数组内容拷贝回原数组
fun rotateArrayByOneUsingCopyIntoMethod(arr: IntArray) {
val newArr = IntArray(arr.size)
arr.copyInto(newArr, 1, 0, arr.size - 1)
newArr.set(0, arr[arr.size - 1])
newArr.copyInto(arr)
}
✅ 优点:代码清晰,利用底层优化,性能接近手动循环
❌ 缺点:需要额外数组,空间开销略大
测试:
@Test
fun `cyclically rotate an array by one position using copyInto method`() {
val arr = intArrayOf(1, 2, 3, 4, 5)
rotateArrayByOneUsingCopyIntoMethod(arr)
assertArrayEquals(intArrayOf(5, 1, 2, 3, 4), arr)
}
4. 使用 takeLast() 与 dropLast()
这是典型的函数式编程风格,代码简洁但有一定性能代价。
takeLast(1)
:获取最后 1 个元素组成的 ListdropLast(1)
:获取除最后 1 个外的所有元素组成的 List
通过 +
拼接两个 List,再转为数组即可。
fun rotateArrayByOneUsingDropLastAndTakeLastMethods(arr: IntArray): Array<Int> {
val newArray = (arr.takeLast(1) + arr.dropLast(1)).toIntArray()
newArray.copyInto(arr)
}
⚠️ 注意:takeLast
和 dropLast
返回的是 List<Int>
,最终需转为 IntArray
并拷贝回原数组。
✅ 优点:代码极简,可读性强
❌ 缺点:创建多个中间对象,频繁 GC 场景慎用
测试:
@Test
fun `cyclically rotate an array by one position using dropLast and takeLast methods`() {
val arr = intArrayOf(1, 2, 3, 4, 5)
rotateArrayByOneUsingDropLastAndTakeLastMethods(arr)
assertArrayEquals(intArrayOf(5, 1, 2, 3, 4), arr)
}
5. 使用 Collections.rotate()
Java 标准库提供了 Collections.rotate()
,Kotlin 中也可直接使用。
但注意:该方法只支持 List
,因此需要先转为列表,旋转后再转回数组。
fun rotateArrayByOneUsingCollectionsRotateMethod(arr: IntArray) {
val list = arr.toList().toMutableList()
Collections.rotate(list, 1)
list.toIntArray().copyInto(arr)
}
📌 参数 1
表示向右旋转 1 位(负数表示向左)
✅ 优点:API 简单,逻辑清晰
❌ 缺点:两次装箱拆箱,性能较差,仅建议在非热点路径使用
测试:
@Test
fun `cyclically rotate an array by one position using Collections rotate methods`() {
val arr = intArrayOf(1, 2, 3, 4, 5)
rotateArrayByOneUsingCollectionsRotateMethod(arr)
assertArrayEquals(intArrayOf(5, 1, 2, 3, 4), arr)
}
6. 递归实现
虽然不推荐在生产环境使用,但递归有助于理解问题本质。
思路:从第一个元素开始,不断将其与最后一个元素交换,直到倒数第二个为止。
fun rotateArrayByOneUsingRecursion(arr: IntArray, position: Int = 0) {
if (position == arr.size - 1) return
val temp = arr[position]
arr[position] = arr[arr.size - 1]
arr[arr.size - 1] = temp
rotateArrayByOneUsingRecursion(arr, position + 1)
}
❌ 严重问题:
- 时间复杂度 O(n),但每次交换都破坏了部分已移动结构
- 实际执行结果并非正确右移(会出错!)
🚫 错误示范:此方法逻辑错误,无法正确实现循环右移,仅供警示用途
测试会失败:
@Test
fun `cyclically rotate an array by one position using recursion`() {
val arr = intArrayOf(1, 2, 3, 4, 5)
rotateArrayByOneUsingRecursion(arr)
// 实际输出可能为 [5, 4, 3, 2, 1] 或其他错误结果
assertArrayEquals(intArrayOf(5, 1, 2, 3, 4), arr) // ❌ 失败
}
✅ 正确递归思路应模拟插入过程,但过于复杂,不如迭代直观。
7. 总结与选型建议
方法 | 时间复杂度 | 空间复杂度 | 是否原地 | 推荐场景 |
---|---|---|---|---|
手动循环 | O(n) | O(1) | ✅ | 高频调用、性能敏感 |
copyInto | O(n) | O(n) | ✅ | 清晰且高效 |
takeLast/dropLast | O(n) | O(n) | ❌ | 脚本或低频场景 |
Collections.rotate | O(n) | O(n) | ❌ | 快速原型 |
递归 | O(n) | O(n) | ❌ | ❌ 不推荐 |
📌 最终建议:
- 生产环境优先使用 手动循环 或 copyInto
- 函数式风格仅用于对性能不敏感的场景
- 避免使用
Collections.rotate
处理基本类型数组 - 递归虽有趣,但容易踩坑,慎用
合理选择方法,才能在代码可读性与运行效率之间取得平衡。