程序员代码面试指南第二版 17.环形链表的约瑟夫问题(普通和进阶)
题目描述
普通版:时间复杂度要求O(n*m)
进阶版:时间复杂度要求O(n)
据说著名犹太历史学家 Josephus 有过以下故事:在罗马人占领乔塔帕特后,39 个犹太人与 Josephus 及他的朋友躲到一个洞中,39 个犹太人决定宁愿死也不要被敌人抓到,
于是决定了一种自杀方式,41 个人排成一个圆圈,由第 1 个人开始报数,报数到 3 的人就自杀,然后再由下一个人重新报 1,报数到 3 的人再自杀,这样依次下去,
直到剩下最后一个人时,那个人可以自由选择自己的命运。这就是著名的约瑟夫问题。现在请用单向环形链表得出最终存活的人的编号。
输入描述:
一行两个整数 n 和 m, n 表示环形链表的长度, m 表示每次报数到 m 就自杀。
输出描述:
输出最后存活下来的人编号(编号从1开始到n)
示例1
输入
5 2
输出
3
普通版
第一次做, 环形链表的形式, 比数组方便处理, 因为不用处理索引越界问题; 用一个指针指向待删除节点的前一个节点; 删除节点后不要更新curr, 因为curr指向的是当前计数节点的前一个节点, 看注释; 一共进行n-1次循环, 内循环至少执行m次, 所以时间复杂度是O(n*m)
import java.util.Scanner;
import java.util.ArrayList;
public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
int n = Integer.parseInt(s.split(" ")[0]);
int m = Integer.parseInt(s.split(" ")[1]);
if(n<1 || m<1)
System.out.println(-1);
if(n==1)
System.out.println(1);
ListNode head = new ListNode(1);
ListNode curr = head;
for(int i=2; i<=n; i++){
curr.next = new ListNode(i);
curr = curr.next;
}
curr.next = head;
int count = 1;
while(curr.next != curr){
while(count!=m){
count++;
curr = curr.next;
}
curr.next = curr.next.next;
count = 1;
}
System.out.println(curr.val);
}
public static class ListNode{
int val;
ListNode next;
ListNode(int val){
this.val = val;
}
}
}
进阶版
在O(n)时间复杂度中解决问题; 核心: 弄明白编号和报数的函数关系, 老编号和新编号的函数关系; 老编号指的是总节点数更多时的编号, 新编号指的是总节点数更少时的编号; 获得幸存者在最初链表中的编号的函数可以通过自顶向下的递归函数实现或者通过自底向上的循环实现
- 编号 = (报数 - 1) % i + 1, 其中i表示链表中节点总个数
- 老编号 = (新编号 + m -1) % i + 1, 其中m表示数到m的自杀, i是老编号所在链表的节点总个数, 这个公式的作用就是, 当我们知道了新编号后, 就可以推出新编号对应的老编号; 幸存节点最后的编号一定是1, 因为到最后的时候链表中只有幸存节点一个了, 于是就可以推出链表中节点有两个的时候幸存节点的编号; 知道了幸存节点在节点总数为2时的编号后, 就能推出幸存节点在节点总数为3时的编号了; 知道了幸存节点在节点总数为3时的编号后, 就能推出幸存节点在节点总数为4时的编号了; 以此类推, 直到知道了幸存节点在节点总数为n-1时的编号后, 就能推出幸存节点在节点总数为n时的编号了
import java.util.Scanner;
public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
int n = Integer.parseInt(s.split(" ")[0]);
int m = Integer.parseInt(s.split(" ")[1]);
if(n<=0 || m<1)
return;
ListNode head = new ListNode(1);
ListNode curr = head;
for(int i=2; i<=n; i++){
curr.next = new ListNode(i);
curr = curr.next;
}
curr.next = head;
int survivorNumInOld = 0;
survivorNumInOld = getNumber(n, m);
int count = survivorNumInOld;
curr = head;
while(survivorNumInOld - 1 > 0){
curr = curr.next;
survivorNumInOld--;
}
curr.next = curr;
System.out.println(curr.val);
}
public static int getNumber(int n, int m){
if(n==1)
return 1;
int newNumber = getNumber(n-1, m);
return (newNumber + m - 1) % n + 1;
}
public static int getNumber2(int n, int m){
int res = 1;
for(int i=2; i<=n; i++){
res = (res + m - 1) % i + 1;
}
return res;
}
public static class ListNode{
int val;
ListNode next;
ListNode(int val){
this.val = val;
}
}
}