嗨! 小伙伴们,我们又见面啦,今天我们来看看这道题: 环形链表的约瑟夫问题
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;
}