第二章 链表问题
2.6 环形单链表的约瑟夫环问题
【题目】
请用单向循环链表描述约瑟夫问题。
- 输入:一个环形单向链表的头节点 head 和报数的值 m。
- 输出:最后生存下来的节点,且这个节点自己组成环形单向链表,其它节点都删掉。
进阶: 如果链表节点数为 N,在时间复杂度为 O(N) 内完成原问题要求。
【难度】
原问题:
士 ★☆☆☆
进阶:
校 ★★★☆
【题解】
普通的解法描述如下:
- 如果链表为空或者链表节点数为 1,或者 m 的值小于 1,则不用调整;
- 在环形链表中遍历每个节点,不断转圈,让每个节点报数;
- 当报树达到 m 时,就删除当前报数的节点;
- 删除节点后,把剩下的节点继续连成环状,继续转圈报数,删除节点;
- 不断地删除节点直到环形链表中只剩下一个节点,过程结束。
对于普通的解法,每删除一个节点,都需要遍历 m 次,一共需要删除的节点数是 N-1 个。所以普通解法的时间复杂度是 O(n×m),这达不到进阶的要求。
进阶的解法描述如下:
- 如果环形链表的节点数为 n,做如下定义:从这个环形链表的头节点开始编号,头节点编号为 1,往后依次是 2、3、…、n。
- 最后只剩下一个节点,这个节点在只由自己组成的环中编号为 1,记为 Num(1)=1;
- 在由两个节点组成的环中,假设最后剩下的节点编号为 Num(2);
… …
- 在由 i-1 个节点组成的环中,假设最后剩下的节点编号为 Num(i-1);
- 在由 i 个节点组成的环中,假设最后剩下的节点编号为 Num(i);
… …
- 在由 n 个节点组成的环中,假设最后剩下的节点编号为 Num(n);
已知 Num(1)=1,确定 Num(i-1) 和 Num(i) 之间的关系,通过递归过程就可以求出 Num(n)。 Num(i-1) 和 Num(i) 之间的关系分析如下:
- 假设现在圈中一共有 i 个节点,从头节点开始报数,依次是 1、2、…、i-1、i、…。假设报 A 的是编号为 B 的节点,则 A 和 B 的关系可以写成:B=(A-1)%i+1,这个表达式不是唯一的。
- 如果编号为 s 的节点被删除,环的节点数从 i 变成了 i-1。新的环只有 i-1 个节点,在编号为 s 的节点的前一个节点,就是编号 s-1 的节点,成了新环中的最后一个节点,也就是编号为 i-1 的节点。假设环大小为 i 的节点编号记为 old,环大小为 i-1 的每个节点编号记为 new,则 old 与 new 之间关系的数学表达式为:old=(new+s-1)%i+1,此表达式同样不唯一。
- 因为每次都是报数到 m 的节点被删除,所以根据步骤 1 的表达式 B=(A-1)%i+1,A=m。被删节点的编号为 (m-1)%i+1,即 s=(m-1)%i+1,代入步骤 2 的表达式 old=(new+s-1)%i+1 中,化简得 old=(new+m-1)%i+1。这就是 Num(i-1) 和 Num(i) 的关系,其只与 m 和 i 有关。
总结如下:
- 遍历链表,求链表的节点个数记为 n,时间复杂度wei O(N);
- 根据 n 和 m 的值,还有 Num(i-1) 和 Num(i) 的关系,递归求最终剩下节点的编号。这一过程递归为 N 层,时间复杂度为 O(N);
- 最后根据最终剩下节点的编号,遍历链表找到该节点,,时间复杂度为 O(N);
- 整个过程结束,总的时间复杂度为 O(N)。
【实现】