今天在浏览技术文章时看到的这么一个问题,感觉甚是有趣,专门来研究研究下,在浏览众多关于约瑟夫讲解的文章后,便进行一个较为详细的总结。
约瑟夫问题,是一个计算机科学和数学中的问题,在计算机编程的算法中,类似问题又称为约瑟夫环,又称“丢手绢问题”。约瑟夫问题在各大刷题网站有各种各样的变体,这里列举了一部分的问题描述,但不管怎么变,其意思都是一样的。
0,1,…,n−1 这 n 个数字排成一个圆圈,从数字 0 开始,每次从这个圆圈里删除第 m 个数字。求出这个圆圈里剩下的最后一个数字。
例如,0、1、2、3、40、1、2、3、4 这 5 个数字组成一个圆圈,从数字 0 开始每次删除第 3 个数字,则删除的前 4 个数字依次是 2、0、4、12、0、4、1,因此最后剩下的数字是 3。
一个由 n 个节点构成的数环,从某个点开始依次编号,然后每一轮剔除第 k 个节点,剩余的形成新的环,从被删除的节点的下一个开始计数,继续剔除,直至只剩下一个节点,求这个节点在最开始 n 个节点中是第几号。
N个人围成一圈,第一个人从1开始报数,报M的将被杀掉,下一个人接着从1开始报。如此反复,最后剩下一个,求最后的胜利者。
已知n个人(以编号1,2,3…n分别表示)围坐在一张圆桌周围。从编号为1的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,要求找到最后一个出列的人
一只猫抓住了n只老鼠,其将老鼠排成一圈,依次按照1~m报数,报m值的吃掉,直到只剩下一只老鼠时,猫将其放生,求获生的老鼠编号。
海盗船长抓到了n个俘虏,但是海盗船只能容许再多一个人,于是他决定让这些俘虏围成一个圈,然后1234的顺序报数,谁报到m就枪毙谁。那么这些俘虏谁可以存活下来呢?
这个就是leetcode1823的题—— 找出游戏的获胜者。这7种描述,不管怎么变,万变不离其宗。对于上述各个问题描述中出现的m,因为m是不确定的,所以没有确切的公式推导,但若是m=2,就有准确的数学递归公式,详情请看约瑟夫环数学问题
对于约瑟夫环问题,我们可以采取模拟队列的方式,将n个元素一一进队,之后将队首元素出队加到队尾,依次加k-1次,第k次要删除的那个元素就是队首元素了,将其出队即可,循环往复,直至队中只剩最后一个元素。返回这个元素即可。
public static int findTheWinner(int n,int k){
Queue<Integer> queue = new ArrayDeque<>();
for (int i = 1; i <= n ; i++) {
queue.offer(i);
}
while (queue.size() != 1){
for (int i = 1; i < k ; i++) {
queue.offer(queue.poll());
}
queue.poll();
}
return queue.peek();
}
对于约瑟夫环问题,我们还可以采用环形链表的方式来解决,构建一个长度为n的链表,各节点值为对应的索引,每轮删除第 m 个节点,直至链表长度为 1 时结束,返回最后剩余节点的值即可。
public static int findTheWinner(int n,int k){
if (k==1){//按一个人计数,最后一个人就是赢家
return n;
}
Node head = new Node(1);
Node tmp = head;
for (int i = 2;i<=n;i++){//建立长度为n的链表
tmp.next = new Node(i);
tmp = tmp.next;
}
tmp.next = head;//构成环
tmp = head;
int i = 0;
while (tmp.val!=tmp.next.val){//当还有一个人的时候,值才会相等,退出循环
i++;
if (i==k-1){
tmp.next = tmp.next.next;//每轮到第k个人,就kill掉
i=0;//然后重新计数
}
tmp = tmp.next;
}
return tmp.val;
}
对于约瑟夫环问题,可以根据动态规划的方式解决,所谓动态规划就是把大问题分成小问题进行处理找到逻辑关系【递推公式】
递归方式:
public static int findTheWinner3(int n,int k){
if (n<=1){
return n;
}
int ans = (findTheWinner3(n-1,k)+k)%n;
return ans== 0? n:ans;
}
采用迭代进行优化:时间复杂度O(n) ,空间复杂度O(1)
public static int findTheWinner2(int n,int k){
int pos = 0;
for (int i = 2; i < n+1; i++) {
pos = (pos + k)%i;
}
return pos+1;
}