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

来源:互联网 发布:apache bench 编辑:程序博客网 时间:2024/06/06 09:06

【题目】

  据说著名犹太历史学家Josephus有过如下故事:在罗马人占领乔塔帕特后,39个犹太人和Josephus及他的朋友躲进一个洞里,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第一个人开始报数,报数到3的人就自杀,再由下一个人重新报1,报数到3的人就自杀,这样依次下去,知道剩下最后一个人时,那个人可以自由选择自己的命运。这就是著名的约瑟夫问题。现在请用单向链表描述该结构并呈现整个自杀过程。
  
  输入:一个环形单向链表的头节点head和报数的值m
  返回:最后生存下来的节点,且这个节点自己组成环形单向链表,其他节点都删掉。

  进阶:如果链表节点数为N,想在时间复杂度为O(N)时完成原问题的要求,该怎么实现?

【基本思路】

  普通解法。
  1、在环形链表中遍历每个节点,不断转圈,不断让每个节点报数。
  2、当报数为m时,就删除当前报数的节点。
  3、删除节点后把剩下的节点继续连成一个环,继续转圈报数,继续删除。
  4、不断的删除,直到只留下一个节点,过程结束。
  普通解法删除一个节点需要遍历m次,一共要删除n-1个节点,所以总的时间复杂度为O(n*m)。

  进阶解法。
  普通解法之所以复杂度高,是因为我们不知道到底哪个节点会留下来。所以依靠不断的遍历删除,直到只留下一个节点。如果我们能不通过遍历,而是直接算出最后活下来的节点是哪个,就可以降低时间复杂度。

  如何计算呢?首先如果环形链表的节点数为n,那么从头节点开始编号,头节点编号为1,下一个编号为2……最后一个节点编号为n。然后考虑如下:

  如果只剩下一个节点,那么幸存的节点就是该节点,编号为1,即Live(1) = 1
  如果剩下两个节点,幸存的节点为Live(2)
  如果剩下三个节点,幸存的节点为Live(3)
  如果剩下i-1个节点,幸存的节点为Live(i-1)
  如果剩下i个节点,幸存的节点为Live(i)
  如果剩下n个节点,幸存的节点为Live(n)
  
  已经知道live(1) = 1,如果再确定Live(i-1)和Live(i)到底是什么关系,我们就可以通过递归过程求出Live(n)。

  首先我们分析如下问题:如果一个节点数为n的链表,编号从头节点到末尾为1~n,如果删除编号为s的一个节点,那么剩下的节点的编号将会如何变化,如图所示。
  这里写图片描述

  设原链表的编号为y,删除一个节点后的编号为x,我们可以得到如下的公式 y = (x + s - 1) % n + 1。因此,我们可以根据Live(i-1)以及被删除的节点编号求得Live(i)的值。那么现在的问题就变成了如何求被删除节点的编号。
  这里写图片描述

  如图所示。对于每一个节点,如果报数值还不到m,就会一直报数下去,1~n~2n……由图我们可以得到一个报数值A与编号B的关系,即B = (A-1) % n + 1。如果报数报到m,报数的节点就是要删除的节点,那么该节点的编号根据公式可以得出 s = (m-1) % n + 1。

  得到被删除节点的编号s,我们就可以得到Live(i-1)与Live(i)之间的关系:Live(i) = (Live(i-1) + s - 1) % i + 1。其中Live(i-1)可以通过向上递归得到,s = (m-1) % i + 1。两式合并后,结果为 Live(i) = (Live(i-1) + m - 1) % i + 1。

  整个过程总结如下:
  1、遍历链表,得到链表的节点数n,O(n)
  2、根据n和m的值,以及上文推导的Live(i)与Live(i-1)的关系,递归求得幸存节点的编号。该递归是单决策递归且递归为n层,所以时间复杂度为O(n)
  3、根据得到的幸存节点的编号,遍历链表找到该节点,O(n)
  所以总体时间复杂度为O(n)。

【代码实现】

#python3.5def josephusKill1(head, m):    if head == None or head.next == None or m < 1:        return head    pre = head    while pre.next != head:        pre = pre.next    count = 1    while head != pre:        if count != m:            head = head.next            pre = pre.next            count += 1        else:            pre.next = head.next            head = pre.next            count = 1    return headdef josephusKill2(head, m):    def getLive(n, m):        if n == 1:            return 1        return (getLive(n-1, m) + m - 1) % n + 1    if head == None or head.next == None or m < 1:        return head    n = 1    cur = head    while cur.next != head:        n += 1        cur = cur.next    n = getLive(n, m)    while n-1 != 0:        n -= 1        head = head.next    head.next = head    return head
原创粉丝点击