据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。
然而Josephus 和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。(最后只能活下来一个人,他还安排他朋友干嘛???)
看了问题的描述我们得到了以下关键信息。
1、是一个圈。
2、从1开始,数到第三个人,把第三个人去掉。然后从下一个重置,继续数到第三个人,去掉,
3、只剩最后一个人时停止。
我们可以使用循环链表来实现这个问题(最后一个元素的next为第一个),用一个不带头结点的循环链表来处理Josephus问题:先构成一个有 n 个结点的单循环链表,然后由从第一个起从1开始计数,计到 m 时,对应结点从链表中删除,然后再从被删除结点的下一个结点又从 1 开始计数,直到只剩最后一个人。
首先我们来创建一个循环链表,思路如下
1、先创建一个节点,让first指向该节点,让first的next也是first,形成自循环。
2、后面没创建一个新的节点,就把该节点加入现有的循环中。(currboy为辅助变量,用于形成循环链表)
如下图所示:
代码如下:
public void add(int nums) { //要整几个节点的环形链表
if (nums < 0) {
System.out.println("nums数值不准确");
return;
}
BoyNode currBoy = new BoyNode();//辅助指针,帮助构建环形链表
for (int i = 0; i < nums; i++) {
BoyNode boy = new BoyNode(i);
if (i == 0) {
first = boy;
first.setNext(first);//构成环
currBoy = first;//当前
} else {
currBoy.setNext(boy);//上一个指向当前
boy.setNext(first);//永远指向第一个构成环
currBoy = boy;//当前后移
}
}
}
然后我们进行第二步,找最后活下来的那个人,首先我们判断一下first为不为空,不为空的话,使用辅助变量currboy遍历,设置一个flag来记录当前遍历到第几个了(记录的是比如第三个人kill,记录到第三个就会重置。)然后循环,当fllag==m时,让currboy.next=currboy.next.next(删除第m个元素),同时将flag重置,然后辅助变量后移,结束条件为currboy.next.no=currboy.no。
见下图
代码如下:
public void Joseph(int killnum){
if (first==null){
System.out.println("此环形链表不存在");
}
BoyNode currBoy=new BoyNode();
currBoy=first;
int flag=1;
while (true){
flag++;
if (flag==killnum){
System.out.println("杀掉=>"+currBoy.getNext().getNo());
currBoy.setNext(currBoy.getNext().getNext());
flag=1;
}
if (currBoy.getNext().getNo()==currBoy.getNo()){
System.out.println("最后活下来的是"+currBoy.getNo());
break;
}
currBoy=currBoy.getNext();
}
}
完整代码如下:
package datastructure.linkedlist.JosephDemo;
public class JosephDemo {
public static void main(String[] args) {
CycleSingleLinkedList cycleSingleLinkedList=new CycleSingleLinkedList();
//总人数
cycleSingleLinkedList.add(6);
cycleSingleLinkedList.list();
cycleSingleLinkedList.Joseph(3);
}
}
class CycleSingleLinkedList {
private BoyNode first = new BoyNode();
public void add(int nums) { //要整几个节点的环形链表
if (nums < 1) {
System.out.println("nums数值不准确");
return;
}
BoyNode currBoy = new BoyNode();//辅助指针,帮助构建环形链表
for (int i = 1; i <= nums; i++) {
BoyNode boy = new BoyNode(i);
if (i == 1) {
first = boy;
first.setNext(first);//构成环
currBoy = first;//当前
} else {
currBoy.setNext(boy);//上一个指向当前
boy.setNext(first);//永远指向第一个构成环
currBoy = boy;//当前后移
}
}
}
// 遍历当前的环形链表
public void list(){
if (first==null){
System.out.println("此环形链表不存在");
}
BoyNode currBoy=new BoyNode();
currBoy=first;
while (true){
if (currBoy.getNext()==first){
System.out.println(currBoy);
break;
}
System.out.println(currBoy);
currBoy=currBoy.getNext();
}
}
//约瑟夫环
public void Joseph(int killnum){
if (first==null){
System.out.println("此环形链表不存在");
}
BoyNode currBoy=new BoyNode();
currBoy=first;
int flag=1;
while (true){
flag++;
if (flag==killnum){
System.out.println("杀掉=>"+currBoy.getNext().getNo());
currBoy.setNext(currBoy.getNext().getNext());
flag=1;
}
if (currBoy.getNext().getNo()==currBoy.getNo()){
System.out.println("最后活下来的是"+currBoy.getNo());
break;
}
currBoy=currBoy.getNext();
}
}
}
class BoyNode {
private int no;//编号
private BoyNode next;//指向下一个节点 如果没有则为空
public BoyNode() {
}
public BoyNode(int no) {
this.no = no;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
public BoyNode getNext() {
return next;
}
public void setNext(BoyNode next) {
this.next = next;
}
@Override
public String toString() {
return "LinkedList{" +
"no=" + no +
'}';
}
}
运行结果如下:
这种方式时间复杂度较高,有一种公式法可参考:换个角度举例解决约瑟夫环 - 圆圈中最后剩下的数字 - 力扣(LeetCode)