算法-27环形链表的约瑟夫问题
描述
输入描述:
输出描述:
示例1
输入: 5 2 输出: 3 备注: 1≤n,m≤10000
思路
普通解法
普通的解法就像题目描述的过程一样,具体实现请参看如下代码中的 josephusKill1 方法。
1.如果链表为空或者链表节点数为 1,或者 m 的值小于 1,则不用调整就直接返回。
2.在环形链表中遍历每个节点,不断转圈,不断让每个节点报数。
3.当报数到达 m 时,就删除当前报数的节点。
4.删除节点后,别忘了还要把剩下的节点继续连成环状,继续转圈报数,继续删除。
5.不停地删除,直到环形链表中只剩一个节点,过程结束。
import java.util.Scanner; // 注意类名必须为 Main, 不要有任何 package xxx 信息 public class Main { public static class Node{ public int value; public Node next; public Node(int value) { this.value = value; } } public static Node josephusKill(Node head,int m) { if(head ==null||head.next == head || m<1) { return head; } Node last = head; while(last.next != head) { last = last.next; } int count = 0; while(head != last) { count++; if(count == m) { last.next = head.next; count = 0; }else{ last = last.next; } head = last.next; } return head; } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); // 注意 hasNext 和 hasNextLine 的区别 int n = scanner.nextInt(); int m = scanner.nextInt(); Node head = new Node(1); Node cur = head; for(int i=2;i<=n;i++) { cur.next = new Node(i); cur = cur.next; } cur.next = head; head = josephusKill(head,m); System.out.println(head.value); } }
进阶解法
原问题之所以花费的时间多,是因为我们一开始不知道到底哪一个节点最后会活下来。所以依靠不断地删除来淘汰节点,当只剩下一个节点的时候,才知道是这个节点。如果不通过一直删除方式,有没有办法直接确定最后活下来的节点是哪一个呢?这就是进阶解法的实质。
这道题的一个复杂之处在于,每次删除了当前数环中的一个元素以后,剩下元素的序号和它的值是不对应的。要想知道最后留下了哪个元素,就需要根据它在数环的相对序号来倒推原始的位置。
我们已知删至只剩一个元素时,该元素的序号为1;假设当数环长度为i时,待删元素的序号为Num(i),Num(1) = 1。我们现在需要明确的就是Num(i)和Num(i-1)的关系。
首先考虑一下,当数环中有i个元素时,报数A的节点编号为B,A与B的关系:B = (A - 1)%i + 1
接下来考虑从长为i的环变为长为i - 1的环时,环中元素的编号变化。假设长为i的环中,被删除元素的编号为d,其余元素在删除元素之前的编号为old,在删除元素以后的编号为new,则new 与 old的关系是:old = (new + s - 1) % i + 1。
由于每次删除的元素报数为m,即A = m, 故B = (m - 1)%i+1,也即 s = (m-1)%i+1;带入后一个等式可知, old = (new + (m-1)%i + 1 - 1)%i+1 = (new + m - 1) % i + 1。
考虑最后剩下的那个节点,它的序号递推公式为:Num(i) = (Num(i - 1) + m - 1) % i + 1,Num(1) = 1。考虑将该公式递推N次,可得原始数组中该元素的序号。
import java.util.Scanner; // 注意类名必须为 Main, 不要有任何 package xxx 信息 public class Main { public static class Node{ public int value; public Node next; public Node(int value) { this.value = value; } } public static Node josephusKill2(Node head,int m) { if(head == null || head.next == head || m<1) { return head; } Node cur = head.next; int tmp = 1; while(cur != head) { tmp++; cur = cur.next; } tmp = getLive(tmp,m); while(--tmp != 0) { head = head.next; } head.next = head; return head; } public static int getLive(int i,int m) { if(i==1) { return 1; } return (getLive(i-1,m)+m-1)%i+1; } public static void main(String[] args) { Scanner scanner = new Scanner(System.in); // 注意 hasNext 和 hasNextLine 的区别 int n = scanner.nextInt(); int m = scanner.nextInt(); Node head = new Node(1); Node cur = head; for(int i=2;i<=n;i++) { cur.next = new Node(i); cur = cur.next; } cur.next = head; head = josephusKill2(head,m); System.out.println(head.value); } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix