约瑟夫环 | 青训营

203 阅读3分钟

进阶班入营考试有一个很有意思的问题 ,和LeetCode 上的 1823 比较像,大概的意思是这样:

共有 n名小伙伴一起做游戏。小伙伴们围成一圈,按 顺时针顺序 从 1 到 n 编号。确切地说,从第 iii 名小伙伴顺时针移动一位会到达第 (i+1) 名小伙伴的位置,其中 1<=i<n ,从第 n 名小伙伴顺时针移动一位会回到第 1 名小伙伴的位置。

游戏遵循如下规则:

  1. 从第 1名小伙伴所在位置 开始 。
  2. 沿着顺时针方向数 k名小伙伴,计数时需要 包含 起始时的那位小伙伴。逐个绕圈进行计数,一些小伙伴可能会被数过不止一次。
  3. 你数到的最后一名小伙伴需要离开圈子,并视作输掉游戏。
  4. 如果圈子中仍然有不止一名小伙伴,从刚刚输掉的小伙伴的 顺时针下一位 小伙伴 开始,回到步骤 2 继续执行。
  5. 否则,圈子中最后一名小伙伴赢得游戏。

给你参与游戏的小伙伴总数 n,和一个整数 k ,返回游戏的获胜者。

这还是一道约瑟夫环经典题。

每次往同一方向,以固定步长 k进行消数。由于下一次操作的发起点为消除位置的下一个点(即前后两次操作发起点在原序列下标中相差 k),同时问题规模会从 n变为 n−1。

以下代码用于模拟一个淘汰游戏。在这个游戏中,有N个人,每次淘汰M个人。代码中使用了一个整数切片a来存储每个人是否被淘汰的状态(0表示未被淘汰,1表示已被淘汰)。通过循环遍历这个切片,当淘汰人数达到M时,输出被淘汰的人的索引/编号。最后输出总共被淘汰的人数。

package main

import "fmt"

func main() {
 var N, M int
   N =10   // 总人数
   M =3     // 每次淘汰的人数

 a := make([]int, N+1) // 创建一个长度为N+1的整数切片,用于存储每个人是否被淘汰的状态(0表示未被淘汰,1表示已被淘汰)

 cnt := 0 // 已淘汰的人数计数器
 i := 0   // 当前人的索引/编号
 k := 0   // 用于记录已经淘汰了几个人,当k达到M时,该人应该被淘汰

 for cnt != N { // 当所有的人都还没有被淘汰时,继续循环
  i++
  if i > N {
   i = 1 // 当i超过N时,从第一个人重新开始计数
  }

  if a[i] == 0 { // 只有值为0的人需要被考虑(尚未被淘汰)
   k++ // k加1,表示已经考虑了一个人
   if k == M { // 当k达到M时,该人应该被淘汰
    a[i] = 1 // 将该人标记为已淘汰状态
    cnt++       // 已淘汰的人数加1
    fmt.Print(i, " ") // 输出被淘汰的人的索引/编号
    k = 0         // 为下一个人重置计数器,从1开始计算
   }
  }
 }
fmt.Print("\n")
// fmt.Print("被淘汰的人数 ",cnt) // 最后输出总共被淘汰的人数
}


这里使用数组方式写了简单的解法。