轮转数组 循环换位解法

算法原理

核心是将元素的移动看作几个独立的闭环

1. 核心目标:元素去哪儿?

首先,我们要明确每个元素的最终归宿。在一个大小为 n 的数组中,向右旋转 k 步,意味着:

  • 索引 i 处的元素,应该移动到索引 (i + k) % n 处。
  • 索引 (i + k) % n 处的元素,应该移动到 ((i + k) % n + k) % n,即 (i + 2k) % n 处。
  • ...以此类推。

我们可以把这个移动关系看作一条"锁链"。

2. 最简单的情况:一个大圈 (Single Cycle)

我们先从最理想的情况开始:所有元素都属于同一个"圈"。

例子:n = 7, k = 3

  • 0 号位的元素要去 3 号位。
  • 3 号位的元素要去 6 号位。
  • 6 号位的元素要去 (6 + 3) % 7 = 2 号位。
  • 2 号位的元素要去 5 号位。
  • 5 号位的元素要去 (5 + 3) % 7 = 1 号位。
  • 1 号位的元素要去 4 号位。
  • 4 号位的元素要去 (4 + 3) % 7 = 0 号位。

我们发现,从 0 号位出发,沿着"要去的位置"一直走,最终会回到 0 号位,并且不重不漏地经过了每一个位置

0 -> 3 -> 6 -> 2 -> 5 -> 1 -> 4 -> 0

既然它们形成了一个完整的闭环,我们就可以这样操作:

  1. 保存起点 :先把 0 号位的元素 nums[0] 拿出来,存在一个临时变量 temp 里。现在 0 号位就"空"了。

  2. 开始换位

    • 4 号位的元素放到 0 号位。现在 4 号位"空"了。
    • 1 号位的元素放到 4 号位。现在 1 号位"空"了。
    • ...
    • 3 号位的元素放到 6 号位。现在 3 号位"空"了。
  3. 终点复位 :最后,把我们一开始保存的 temp (也就是最初 0 号位的元素) 放入最后空出来的 3 号位。

这样,我们只用了一个临时变量的空间,就完成了所有元素的交换。

3. 复杂的情况:多个独立的圈 (Multiple Cycles)

那是不是所有情况都这么理想呢?并不是。

例子:n = 6, k = 2

  • 0 号位出发:0 -> 2 -> 4 -> (4 + 2) % 6 = 0

    • 这是一个小圈!它只涉及了 0, 2, 4 三个位置的元素。
    • 1, 3, 5 号位置的元素完全没有被触碰到。
  • 我们从未被触碰过的 1 号位再出发:1 -> 3 -> 5 -> (5 + 2) % 6 = 1

    • 这是另一个独立的小圈!它只涉及了 1, 3, 5 三个位置。

在这个例子里,整个数组被巧妙地分解成了两个完全独立的圈。我们只需要对每个圈分别执行上面"单圈"的换位逻辑,就能完成整个数组的旋转。

4. 原理的核心:最大公约数 (GCD)

现在最关键的问题来了:到底会有多少个独立的圈呢?

数学家们已经证明,这个圈的数量不多不少,正好是数组大小 n 和旋转步数 k 的最大公约数 ,即 gcd(n, k)

让我们验证一下:

  • n = 7, k = 3: gcd(7, 3) = 1。所以只有 1 个大圈。
  • n = 6, k = 2: gcd(6, 2) = 2。所以有 2 个独立的圈。
  • n = 12, k = 8: gcd(12, 8) = 4。所以会有 4 个独立的圈,每个圈里有 12 / 4 = 3 个元素。

并且,这 gcd(n, k) 个圈的起始点,正好就是数组的前 gcd(n, k) 个位置:0, 1, 2, ..., gcd(n, k) - 1

5. 总结:完整的算法原理

综合以上分析,我们可以得出"循环换位"算法的完整逻辑:

  1. 确定圈数 :计算 g = gcd(n, k)。这告诉我们总共有 g 个独立的换位循环。

  2. 遍历每个圈 :写一个外层循环,从 i = 0g - 1。每一次循环处理一个完整的圈,这个圈的起始点就是 i

  3. 对圈内进行换位:在内层循环里,执行我们上面"单圈"的逻辑:

    • temp 保存 nums[i] 的值。
    • i 号位开始,沿着"锁链"把前一个位置的元素搬过来,直到绕回 i
    • temp 放到最后空出的位置。
  4. 当外层循环结束时,所有 g 个圈都已完成换位,整个数组也就旋转完毕了。

这个算法的精妙之处在于,它通过数论(最大公约数)预见了数组内元素的移动模式,并将一个复杂的操作分解为几个简单、独立的循环,从而在不使用额外数组空间的情况下,高效地完成了任务。

代码实现

py 复制代码
import math
def solution(nums, n, k):
    g = math.gcd(n, k) # 计算小圈的个数
    k %= n
    for i in range(g):
        tmp = nums[i] # 保存每圈的第一个元素
        cur = i
        while True:
            pre = (cur - k) % n # 计算出当前位置在旋转后应存放元素的位置索引
            if pre == i: # 追溯到圈的起点,本圈结束
                break
            nums[cur] = nums[pre]
            cur = pre
        nums[cur] = tmp # 将最开始保存的元素(temp),放到最后空出的位置上

if __name__ == "__main__":
    n = int(input())
    nums = list(map(int, input().split()))
    k = int(input())
    solution(nums, n, k)
    print(" ".join(map(str, nums)))
相关推荐
hrrrrb27 分钟前
【密码学】1. 引言
网络·算法·密码学
lifallen33 分钟前
KRaft 角色状态设计模式:从状态理解 Raft
java·数据结构·算法·设计模式·kafka·共识算法
雲墨款哥1 小时前
算法练习-Day1-交替合并字符串
javascript·算法
fishcanf1y1 小时前
记一次与Fibonacci斗智斗勇
算法
Mr_Xuhhh2 小时前
QT窗口(3)-状态栏
java·c语言·开发语言·数据库·c++·qt·算法
啊这.-2 小时前
Codeforces Round 921 (Div. 1) B. Space Harbour(线段树,2100)
算法
远望樱花兔2 小时前
【Java】【力扣】94.二叉树的中序遍历
算法·leetcode
Tim_102 小时前
【算法专题训练】03、比特位计数
c++·算法
张人玉2 小时前
C#`Array`进阶
java·算法·c#
MicroTech20252 小时前
Bell不等式赋能机器学习:微算法科技MLGO一种基于量子纠缠的监督量子分类器训练算法技术
科技·算法·量子计算