【刷题 issue16】程序员代码面试指南 —— IT 名企算法与数据结构题目最优解

第二章 链表问题

2.6 环形单链表的约瑟夫环问题

【题目】

请用单向循环链表描述约瑟夫问题。

  • 输入:一个环形单向链表的头节点 head 和报数的值 m。
  • 输出:最后生存下来的节点,且这个节点自己组成环形单向链表,其它节点都删掉。

进阶: 如果链表节点数为 N,在时间复杂度为 O(N) 内完成原问题要求。

【难度】

原问题:

士 ★☆☆☆

进阶:

校 ★★★☆

【题解】

普通的解法描述如下:

  1. 如果链表为空或者链表节点数为 1,或者 m 的值小于 1,则不用调整;
  2. 在环形链表中遍历每个节点,不断转圈,让每个节点报数;
  3. 当报树达到 m 时,就删除当前报数的节点;
  4. 删除节点后,把剩下的节点继续连成环状,继续转圈报数,删除节点;
  5. 不断地删除节点直到环形链表中只剩下一个节点,过程结束。

对于普通的解法,每删除一个节点,都需要遍历 m 次,一共需要删除的节点数是 N-1 个。所以普通解法的时间复杂度是 O(n×m),这达不到进阶的要求。

进阶的解法描述如下:

  1. 如果环形链表的节点数为 n,做如下定义:从这个环形链表的头节点开始编号,头节点编号为 1,往后依次是 2、3、…、n。
  2. 最后只剩下一个节点,这个节点在只由自己组成的环中编号为 1,记为 Num(1)=1;
  3. 在由两个节点组成的环中,假设最后剩下的节点编号为 Num(2);

… …

  1. 在由 i-1 个节点组成的环中,假设最后剩下的节点编号为 Num(i-1);
  2. 在由 i 个节点组成的环中,假设最后剩下的节点编号为 Num(i);

… …

  1. 在由 n 个节点组成的环中,假设最后剩下的节点编号为 Num(n);

已知 Num(1)=1,确定 Num(i-1) 和 Num(i) 之间的关系,通过递归过程就可以求出 Num(n)。 Num(i-1) 和 Num(i) 之间的关系分析如下:

  1. 假设现在圈中一共有 i 个节点,从头节点开始报数,依次是 1、2、…、i-1、i、…。假设报 A 的是编号为 B 的节点,则 A 和 B 的关系可以写成:B=(A-1)%i+1,这个表达式不是唯一的。
  2. 如果编号为 s 的节点被删除,环的节点数从 i 变成了 i-1。新的环只有 i-1 个节点,在编号为 s 的节点的前一个节点,就是编号 s-1 的节点,成了新环中的最后一个节点,也就是编号为 i-1 的节点。假设环大小为 i 的节点编号记为 old,环大小为 i-1 的每个节点编号记为 new,则 old 与 new 之间关系的数学表达式为:old=(new+s-1)%i+1,此表达式同样不唯一。
  3. 因为每次都是报数到 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 有关。

总结如下:

  1. 遍历链表,求链表的节点个数记为 n,时间复杂度wei O(N);
  2. 根据 n 和 m 的值,还有 Num(i-1) 和 Num(i) 的关系,递归求最终剩下节点的编号。这一过程递归为 N 层,时间复杂度为 O(N);
  3. 最后根据最终剩下节点的编号,遍历链表找到该节点,,时间复杂度为 O(N);
  4. 整个过程结束,总的时间复杂度为 O(N)。

【实现】


发布了147 篇原创文章 · 获赞 72 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/Pranuts_/article/details/100171567