【每日一题2|难度:中等】约瑟夫环

201 阅读2分钟

retouch_2023072715460254.jpg

约瑟夫问题:有30只猴子,按顺时针方向围成一圈选大王(编号从1到30),从第1号开始报数,一直数到3,数到3的猴子退出圈外,剩下的猴子再接着从1开始报数。就这样,直到圈内只剩下一只猴子时,这个猴子就是猴王,求输出最后猴王的编号。

是不是有点没头绪?不急,不管怎么样,先来初始化一个数组把这30只猴子装进去

  const createArr = (nums) => {
    const arr = []
    for (let i = 0; i < nums; i++) {
      arr[i] = i + 1
    }
    return arr
  }

现在我们得到了这组猴子的数组,每个位置对应一只猴

我们来看看这组数组需要什么

首先,无论退出与否,猴子的总个数是不变的,而且——每只猴子的编号是 唯一的 不变的 ,这说明我们不要对这个数组做任何的删除操作,否则将会打乱编号

在这里我有想过用map或者对象来保存那唯一不变的编号,这样就算删除某一项,key是不会变的,但很快我就排除了这种做法,那会更复杂,因为不好遍历。

其次,如果不删除数组,那要怎样标识推出的猴子呢?

注意题目中编号是从1开始算的,只要将推出的猴子当前数组的值改为一个假值即可,比如0或者-1,我们只需要遍历当前真值的猴子,让他们报数即可。

// 初始化变量 
let curIndex = 0 //当前数组下标 
let counter = 0 //报数号码(1,2,3) 
let deleteNums = 0 //删除的数目
// 只要当前位置未被删除标记0,就报数 
if (!(arr[curIndex] === 0)) { 
    counter += 1 
}
// 当前报的数是3就将当前数组置为0表示删除,删除数目加1,报数号码置0 
if (counter === count) { 
    counter = 0 
    deleteNums += 1 
    arr[curIndex] = 0 
}

最后,怎样连成一个环以及什么时候退出这个循环?

每次循环走到尾部将下标置0返回首部就可以一直遍历数组了,如果当前退出的猴子个数超过了猴子的总数目,那毫无疑问,肯定是要退出循环的

所以现在给出两个变量:deleteNumsnums,请问 while 循环的条件是什么?

你可能会一秒脱口而出—— deleteNums < nums 时,可以循环。别那么快回答,再想两秒钟,下面是完整代码


  const getRes = (nums, count) => {
    // 初始化数组
    const arr = createArr(nums)
    // 初始化变量
    let curIndex = 0 //当前数组下标
    let counter = 0 //报数号码(123)
    let deleteNums = 0 //删除的数目

    //只要删除数目小于29就一直循环报数
    while (deleteNums<nums-1) {
      // 只要当前位置未被删除标记0,就报数
      if (!(arr[curIndex] === 0)) {
        counter += 1
      }
      // 当前报的数是3就将当前数组置为0表示删除,删除数目加1,报数号码置0
      if (counter === count) {
        counter = 0
        deleteNums += 1
        arr[curIndex] = 0
      }
      // 数组下标加加
      curIndex++;

      // 数组下标到达尾部了就置为 0
      if (curIndex === arr.length) {
        curIndex = 0
      }
    }
    // 退出循环后,return出不为 0的小孩编号
    return arr?.filter(item =>item>0)[0]

  }
  console.log(getRes(30, 3))