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

本文介绍了约瑟夫问题在环形单链表中的应用,给出了解决方法。从普通解法到进阶解法,详细阐述了如何在O(n)时间复杂度内找到最后存活的节点,并提供了相应的递归算法分析。
摘要由CSDN通过智能技术生成

【题目】

  据说著名犹太历史学家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。

  定义Live(int n)函数,表示如果有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的一个节点,那么剩下的节点的编号将会如何变化,如图所示。
  这里写图片描述

  设原链表中节点的编号为 old,删除一个节点后的编号为 new,删除的节点的编号记为 s,我们可以得到如下的公式 old = (new + 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.5
def 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 head


def 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值