约瑟夫问题:有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返回首部就可以一直遍历数组了,如果当前退出的猴子个数超过了猴子的总数目,那毫无疑问,肯定是要退出循环的
所以现在给出两个变量:deleteNums,nums,请问 while 循环的条件是什么?
你可能会一秒脱口而出—— deleteNums < nums 时,可以循环。别那么快回答,再想两秒钟,下面是完整代码
const getRes = (nums, count) => {
// 初始化数组
const arr = createArr(nums)
// 初始化变量
let curIndex = 0 //当前数组下标
let counter = 0 //报数号码(1,2,3)
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))