每日一题---OJ题: 环形链表的约瑟夫问题

嗨! 小伙伴们,我们又见面啦,今天我们来看看这道题:  环形链表的约瑟夫问题

emmm,题目看似不太好理解,咱们画个图吧!

第一次:   从1号位置开始报数,从1开始,编号为2的人离开,1号->1,2号->2,2号离开。

第二次: 再从剩下的1号,3号,4号,5号里面报数,从3号开始报数,3号->1,4号->2,4号离开。

 

第三次:从剩下的1号,3号,5号里面报数,从5号开始报数,5号->1, 1号->2, 1号离开。

第四次:从剩下的3号,5号里面报数,从3号开始报数,3号->1,5号->2, 5号离开,最后留下来的是3号。

好的,图画清晰了,该怎么做呢?

思路一: 我们可以根据n来创建不带头单向循环链表,逢m就删除当前结点。

我们可以先定义一个不带头单向循环链表,因为建立链表需要创建结点,因此,我们把创建新结点封装成函数,方便之后进行调用。

创建新结点的代码如下:

typedef struct ListNode ListNode;
ListNode* BuyNode(int x) {
    //创建一个新结点
    //向内存中申请一块空间
    ListNode* newNode = (ListNode*) malloc(sizeof(ListNode));   
    if (newNode == NULL) {
    //如果申请失败,直接退出
        exit(1);
    }
    newNode->val = x;         //新结点的数据域为x
    newNode->next = NULL;     //新结点的next指针置为空
    return newNode;           //将新结点返回
}

创建好了新结点,相当于有了一块块单独的羊肉块,我们需要把它们像羊肉串一样串起来 ,放在循环链表中。

怎么创建循环链表呢? 别着急,我们画图分析分析

(这里假设n为5,循环链表总共有5个结点)

Step1:   我们定义一个头结点,一个尾结点,同时指向第一个结点

第一次:   链表的头结点和尾结点都指向第一个结点,新结点的next指针指向NULL。

第二次: 头结点不变,新结点尾插到链表中,作为新的尾结点。

第三次:

第四次:

第五次:

咦? 奇怪,这样就是单链表了呀,怎么循环起来呢?

很简单,就让 newTail 的 next 指针指向 newHead 就可以了啊!

创建不带头单向循环链表的代码如下:

ListNode* CreateList(int n) {
    //创建循环链表
    ListNode* phead = BuyNode(1);       //链表的头结点存在,它的数据域为1
    ListNode* ptail = phead;            //链表的头结点和尾结点指向同一个结点
    for (int i = 2; i <= n; i++) {
        //依次创建第2个结点,第3个结点....第n个结点
        ptail->next = BuyNode(i);
        ptail =  ptail->next;
    }
    //让尾结点的next指针指向头结点,使其首尾相连,循环起来
    ptail->next = phead;
    //返回头结点
    return phead;    
}

准备工作就绪后,下面就是重头戏啦!

首先,我们定义一个pcur变量,用来遍历这个循环链表的每一个结点; 定义一个prev变量,用来保存pcur指向的结点的前驱结点 ; 定义一个计数器count , 用来统计这是第几次。

(如果count的值不为m, 那么pcur继续指向下一个结点, 如果count的值为m , 那么删除当前pcur指向的结点)

第一次: prev初始置为NULL, pcur指向头结点(“1”位置), count为1,pcur将遍历下一个结点。

    int count = 1;                       //用来记录此时的序号
    ListNode* head = CreateList(n);
    ListNode* pcur = head;              //pcur指向头结点
    ListNode* prev = NULL;              //prev置为NULL

第二次: pcur指向下一个结点(“2”位置),prev保存pcur指向结点的前一个结点,count为2,执行删除操作。

此时我们要删除pcur指向的结点, prev指向pcur指向结点的后继结点,pcur将指向下一个结点,count置为初始值1。

            //删除当前节点pcur
            prev->next = pcur->next;
            free(pcur);
            //删除pcur结点后,要让pcur走到新的位置,count置为初始值
            pcur = prev->next;
            count = 1;

或者这样写,也可以:

            //删除当前节点pcur
            prev->next = pcur->next;
            ListNode* del = pcur;            
            //用临时变量del将pcur指向的结点保存下来
            pcur = pcur->next;
            //删除pcur结点后,要让pcur走到新的位置,count置为初始值
            free(del);
            count = 1;

第三次:pcur指向下一个结点(“3”位置),prev和pcur指向同一个结点,count为1,pcur将指向下一个结点。

代码如下:

         prev = pcur;       //prev指针保留pcur的前一个结点
         pcur = pcur->next; //pcur结点继续往后走,
         count++;           //计数器自增一次

第四次: pcur指向下一个结点(“4”位置),prev保存pcur指向结点的前一个结点,count为2

此时我们需要删除pcur指向的结点,prev的next指针指向pcur结点的后继结点,释放pcur结点,pcur为野指针,pcur将走到新的位置,count置为初始值1。

第五次:pcur指向下一个结点(“5”位置),prev和pcur指向同一个结点,count为1,pcur将指向下一个结点。

第六次: pcur指向下一个结点(“1”位置),prev保存pcur指向结点的前一个结点,count为2

此时我们需要删除pcur指向的结点,prev的next指针指向pcur结点的后继结点,释放pcur结点,pcur为野指针,pcur将走到新的位置,count置为初始值1。

第七次:pcur指向下一个结点(“3”位置),prev和pcur指向同一个结点,count为1,pcur将指向下一个结点。

第八次: pcur指向下一个结点(“5”位置),prev保存pcur指向结点的前一个结点,count为2。

此时我们需要删除pcur指向的结点,prev的next指针指向pcur结点的后继结点,释放pcur结点,pcur为野指针,pcur将走到新的位置,count置为初始值1。

第九次:pcur指向下一个结点(“3”位置),prev和pcur指向同一个结点,count为1,pcur将指向下一个结点。

此时下一个结点仍然是“3”位置,因此跳出while循环。

所以,进入while循环的条件是什么呢? 猜对啦,答案就是: pcur->next != pcur;

当链表中只有一个结点,即退出循环。

核心代码如下:


    int count = 1;                       //用来记录此时的序号
    ListNode* head = CreateList(n);
    ListNode* pcur = head;              //pcur指向头结点
    ListNode* prev = NULL;              //prev置为NULL
    while(pcur->next != pcur){
        //当链表中不止有一个结点时,进入循环(当链表中只有1个结点时,就退出循环)
        if(count == m){
            //逢m就删除当前结点
            prev->next = pcur->next;
            ListNode* del = pcur;
            pcur = pcur->next;
            //删除pcur结点后,要让pcur走到新的位置,count置为初始值
            free(del);
            count = 1;
        }else{

         prev = pcur;       //prev指针保留pcur的前一个结点
         pcur = pcur->next; //pcur结点继续往后走,
         count++;           //计数器自增一次
      }
    }

题目要求我们最后返回整型int , 即 pcur指向结点的数据域 , 那么就返回 pcur->val。

好啦,讲到这里就结束啦,附上完整代码:

typedef struct ListNode ListNode;
ListNode* BuyNode(int x) {
    //创建一个新结点
    //向内存中申请一块空间
    ListNode* newNode = (ListNode*) malloc(sizeof(ListNode));   
    if (newNode == NULL) {
    //如果申请失败,直接退出
        exit(1);
    }
    newNode->val = x;         //新结点的数据域为x
    newNode->next = NULL;     //新结点的next指针置为空
    return newNode;           //将新结点返回
}

ListNode* CreateList(int n) {
     //根据n来创建不带头的单向循环链表
    ListNode* phead = BuyNode(1);       //链表的头结点存在,它的数据域为1
    ListNode* ptail = phead;            //链表的头结点和尾结点指向同一个结点
    for (int i = 2; i <= n; i++) {
        //依次创建第2个结点,第3个结点....第n个结点
        ptail->next = BuyNode(i);
        ptail =  ptail->next;
    }
    //让尾结点的next指针指向头结点,使其首尾相连,循环起来
    ptail->next = phead;
    //返回头结点
    return phead;    
}
int ysf(int n, int m ) {
 
    int count = 1;                       //用来记录此时的序号
    ListNode* head = CreateList(n);
    ListNode* pcur = head;              //pcur指向头结点
    ListNode* prev = NULL;              //prev置为NULL
    while(pcur->next != pcur){
        //当链表中不止有一个结点时,进入循环(当链表中只有1个结点时,就退出循环)
        if(count == m){
            //逢m就删除当前结点
            prev->next = pcur->next;
            ListNode* del = pcur;
            pcur = pcur->next;
            //删除pcur结点后,要让pcur走到新的位置,count置为初始值
            free(del);
            count = 1;
        }else{
           
         prev = pcur;       //prev指针保留pcur的前一个结点
         pcur = pcur->next; //pcur结点继续往后走,
         count++;           //计数器自增一次
      }
    }
    //此时pcur结点就是幸存下来的唯一结点
    return pcur->val;
}

OK,这个boss还是被我们打败了,我们下期再见! 

  • 27
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值