手撕代码----约瑟夫环问题
据说著名犹太历史学家 Josephus 有过以下故事:在罗马人占领乔塔帕特后,39 个犹太人与 Josephus 及他的朋友躲到一个洞中,39 个犹太人决定宁愿死也不要被敌人抓到,于是决定了一种自杀方式,41 个人排成一个圆圈,由第 1 个人开始报数,报数到 3 的人就自杀,然后再由下一个人重新报 1,报数到 3 的人再自杀,这样依次下去,直到剩下最后一个人时,那个人可以自由选择自己的命运。这就是著名的约瑟夫问题。现在请用单向环形链表得出最终存活的人的编号。
题目链接
输入描述
一行两个整数 n 和 m, n 表示环形链表的长度, m 表示每次报数到 m 就自杀。
输出描述
输出最后存活下来的人编号(编号从1开始到n)
基本思路
- 考虑特殊情况,比如只有一个节点,m<1等情况
- 新建链表结点指针,使得 last.next = head。这样的原因是当head所在节点为需要删除的节点时,可以利用head的前驱节点last进行断链、连接等操作方便删除节点。
- 当最后只有一个节点时,即head = last就输出节点的值。
- 时间复杂度分析:每次删除一个节点需遍历m次,一共需要删除n-1个节点,时间复杂度为O(m*n).
import java.util.*;
//创建节点类,为了创建环形链表
class Node{
int val;
Node next;
public Node(){}
public Node(int val){
this.val = val;
}
public Node(int val, Node next){
this.val = val;
this.next = next;
}
}
public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
while(sc.hasNext()){
int num = sc.nextInt();
int m = sc.nextInt();
Node head = new Node(1);
Node p = head;
//创建单链表
for(int i=2;i<=num;i++){
Node node = new Node(i);
p.next = node;
p = p.next;
}
//成环操作
p.next = head;
System.out.println(solution(head, m));
}
}
//解决问题的函数
public static int solution(Node head, int m){
//特殊情况考虑
if(head==null||head.next==null||m<1) return head.val;
Node last = head;
//指定last指针,满足思路2
while(last.next!=head){
last = last.next;
}
int count = 1;
while(head!=last){
//删除节点操作
if(count==m){
head = head.next;
last.next = head;
count = 1;
}else if(count<m){
head = head.next;
last = last.next;
count++;
}
}
return head.val;
}
}
进阶思路(时间复杂度O(N))
数学归纳法,递归实现
(1,2,3,…,m-1,m,m+1,…,n)—>(n-m+1,n-m+2,…,n-1,1,…, n-m)
数学归纳公式:F(n) = (F(n-1)+m-1)%n+1(未完待续)
//解决问题的主函数
public static int solution(Node head, int m){
//特殊情况考虑
if(head==null||head.next==null||m<1) return head.val;
Node last = head.next;
int temp = 1;
while(last!=head){
temp++;
last = temp.next;
}
temp = getLive(temp, m);
while(--temp!=0){
head = head.next;
}
head.next = head;
return head.val;
}
public static int getLive(int i, int m){
if(i==1) return 1;
return (getLive(i-1, m)+m-1) % i+1;
}