1. 概述

在实际开发中,数组的循环移位是一个常见需求,比如在滑动窗口、队列操作或数据重排场景中都会遇到。所谓“循环右移一位”,就是将数组最后一个元素移到首位,其余元素整体后移一位。

本文将系统介绍在 Kotlin 中实现数组循环右移一位的几种方法。目标是帮助你在不同场景下选择最合适的方案,避免踩坑。✅

示例:[1, 2, 3, 4, 5][5, 1, 2, 3, 4]


2. 手动循环法(原地操作)

这是最基础也是性能最高的方式,适合对时间复杂度敏感的场景。核心思路是:

  1. 用临时变量保存最后一个元素
  2. 从后往前遍历,依次将前一个元素赋值给当前元素
  3. 最后把临时变量赋给首元素
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() 方法用于高效复制数组片段。我们可以利用它来避免手动写循环。

关键参数说明(按顺序):

  • 目标数组
  • 目标数组的起始偏移量
  • 源数组的起始索引
  • 源数组的结束索引(不包含)

实现方式:

  1. 将前 n-1 个元素复制到新数组的第 1 位开始的位置
  2. 将原数组最后一个元素放入新数组第 0 位
  3. 将新数组内容拷贝回原数组
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 个元素组成的 List
  • dropLast(1):获取除最后 1 个外的所有元素组成的 List

通过 + 拼接两个 List,再转为数组即可。

fun rotateArrayByOneUsingDropLastAndTakeLastMethods(arr: IntArray): Array<Int> {
    val newArray = (arr.takeLast(1) + arr.dropLast(1)).toIntArray()
    newArray.copyInto(arr)
}

⚠️ 注意takeLastdropLast 返回的是 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 处理基本类型数组
  • 递归虽有趣,但容易踩坑,慎用

合理选择方法,才能在代码可读性与运行效率之间取得平衡。


原始标题:Cyclically Rotate Array by One in Kotlin