主要思路:
维护一个循环链表
代码:
一:自己版本(动态链表,而且还带着明显c语言痕迹)
#include <iostream>
using namespace std;
typedef struct People {
int val;
People* next;
People(int x) : val(x), next(nullptr) {}
}People;
//创建人员序号链表
People* createListNode(int n) {
People* fakeHead = (People* )malloc(sizeof(People)); //利用虚拟头结点
fakeHead -> next = nullptr;
People* p = fakeHead;
for(int i = 1; i <= n; i++) {
p -> next = (People* )malloc(sizeof(People));
p -> next -> val = i;
p = p -> next; //第一次写错误:移动指针必须下一个节点已经创建
p -> next = nullptr;
}
p -> next = fakeHead -> next; //关键在这里构成循环链表
return fakeHead;
}
int main(void) {
int n, m;
cin >> n >> m;
People* fakeHead = createListNode(n);
int count = 1;
People* slow = fakeHead -> next; //这个指针始终停在待删除链表之前
while(slow -> next != slow) { //循环链表里只剩一个元素
if(count == m - 1) {
People* fast = slow -> next;
cout << fast -> val << ' ';
slow -> next = fast -> next;
free(fast);
slow = slow -> next; //先删后一个节点再移动指针 slow
count = 1;
}
slow = slow -> next;
count++;
}
cout << slow -> val;
free(fakeHead);
return 0;
}
二: 用结构体数组维护单向静态链表
//洛谷P1996,结构体数组实现双向静态链表
#include <bits/stdc++.h>
const int N = 105;
struct node{ //双向链表
int id; //结点编号
//int data; //如有必要,定义一个有意义的数据
int preid, nextid; //前一个结点,后一个结点
}nodes[N];
int main(){
int n, m; scanf("%d%d", &n, &m);
nodes[0].nextid = 1;
for(int i = 1; i <= n; i++){ //建立链表
nodes[i].id = i;
nodes[i].preid = i-1; //前结点
nodes[i].nextid = i+1; //后结点
}
nodes[n].nextid = 1; //循环链表:尾指向头
nodes[1].preid = n; //循环链表:头指向尾
int now = 1; //从第1个开始
while((n--) >1){
for(int i=1;i<m;i++) now = nodes[now].nextid; //数到m,停下
printf("%d ", nodes[now].id); //打印,后面带空格
int prev = nodes[now].preid, next = nodes[now].nextid; //这里体现了双向链表的特性
nodes[prev].nextid = nodes[now].nextid; //删除now
nodes[next].preid = nodes[now].preid;
now = next; //新的开始
}
printf("%d", nodes[now].nextid); //打印最后一个,后面不带空格
return 0;
}
注:大数组定义在全局变量里防止爆栈
三:用结构体数组维护双向静态链表
#include <bits/stdc++.h>
const int N = 105;
struct node{ //双向链表
int id; //结点编号
//int data; //如有必要,定义一个有意义的数据
int preid, nextid; //前一个结点,后一个结点
}nodes[N];
int main(){
int n, m; scanf("%d%d", &n, &m);
nodes[0].nextid = 1;
for(int i = 1; i <= n; i++){ //建立链表
nodes[i].id = i;
nodes[i].preid = i-1; //前结点
nodes[i].nextid = i+1; //后结点
}
nodes[n].nextid = 1; //循环链表:尾指向头
nodes[1].preid = n; //循环链表:头指向尾
int now = 1; //从第1个开始
while((n--) >1){
for(int i=1;i<m;i++) now = nodes[now].nextid; //数到m,停下
printf("%d ", nodes[now].id); //打印,后面带空格
int prev = nodes[now].preid, next = nodes[now].nextid; //这里体现了双向链表的特性
nodes[prev].nextid = nodes[now].nextid; //删除now
nodes[next].preid = nodes[now].preid;
now = next; //新的开始
}
printf("%d", nodes[now].nextid); //打印最后一个,后面不带空格
return 0;
}
四:用STL中list
//洛谷P1996,STL list
#include <bits/stdc++.h>
using namespace std;
int main(){
int n, m; cin>>n>>m;
list<int>node;
for(int i=1;i<=n;i++) node.push_back(i); //建立链表
list<int>::iterator it = node.begin();
while(node.size()>1){ //list的大小由STL自己管理
for(int i=1;i<m;i++){ //数到m
it++;
if(it == node.end()) it = node.begin(); //循环:到末尾了再回头
}
cout << *it <<" ";
list<int>::iterator next = ++it;
if(next==node.end()) next=node.begin(); //当next就是末尾,则next这个节点一定不会在这轮删除,则根据游戏规则直接将next定义为开头重新报数
node.erase(--it); //因为之前it已经++,所以这里再--,it就有指向待删除节点,删除这个结点后,node.size()自动减1
it = next;
}
cout << *it;
return 0;
}
总结:
(一)如果是单向链表用双指针,则cur指向当前要删除元素,然后pre指向cur下一个,将cur释放后cur也顺势指向pre下一个,继续循环
如果是双向链表,可以利用回退特性只用一个指针即可
(二)最后一个元素都要单独处理
主要思路
一开始用STL中list写,结果超时
#include <bits/stdc++.h>
#include <list>
using namespace std;
int main(void) {
int N;
scanf("%d", &N);
// 若 p 为 0,
// 则表示将 i 号同学插入到 k 号同学的左边,
// p为 1则表示插入到右边。
list<int>node;
node.push_back(1);
int k, p;
for(int i = 2; i <= N; i++) {
scanf("%d %d", &k, &p);
list<int>::iterator it = node.begin();
if(p == 0) {
while(*it != k) {
it++;
}
node.insert(it, i);
}
else if(p == 1) {
while(*it != k) {
it++;
}
node.insert(++it, i);
}
}
// list<int>::iterator it;
// cout << "---------------------------" << endl;
// for(it = node.begin(); it != node.end(); ++it) {
// cout << *it << ' ';
// }
// cout << endl;
// cout << "---------------------------" << endl;
int M;
scanf("%d", &M);
for(int i = 0; i < M; i++) {
int x;
scanf("%d",&x);
for(list<int>:: iterator it= node.begin(); it != node.end(); it++) {
if(*it == x) {
node.erase(it);
break;
}
}
}
for(list<int>:: iterator it = node.begin(); it != node.end(); it++) {
printf("%d ", *it);
}
return 0;
}
改变思路,用静态数组实现双向链表
(1)数组下标表示的是第k号同学
(2)这是最关键的理解:其实数组连续的存储空间与链表非连续存储空间在这里可以理解为没有区别,关键是利用结构里的前后指针去“找”前一个节点与后一个节点
(3)插入节点时要同时修改四个指针
#include <bits/stdc++.h>
#include <list>
const int NUM = 100005;
struct node {
int preid, val, nextid;
node() : preid(0), val(0), nextid(0){};
}node[NUM];
using namespace std;
int main(void) {
int N;
scanf("%d", &N);
// 若 p 为 0,
// 则表示将 i 号同学插入到 k 号同学的左边,
// p为 1则表示插入到右边。
node[1].val = 1; //用节点下标表示第几号同学,因为下标与同学序号均非负
int k, p, count = 1;
for(int i = 2; i <= N; i++) {
scanf("%d %d", &k, &p);
if(p == 0) { //在两个节点中插入,又有两个指针,所以要修改四处
int prenode = node[k].preid;
node[prenode].nextid = i;
node[i].preid = prenode;
node[i].nextid = k;
node[k].preid = i;
node[i].val = 1; //所添加节点状态
}
else if(p == 1) {
int nextnode = node[k].nextid;
node[k].nextid = i;
node[i].preid = k;
node[i].nextid = nextnode;
node[nextnode].preid = i;
node[i].val = 1;
}
}
// for(int i = 1; i <= N; i++) {
// if(node[i].val) {
// cout << i << ' ' << node[i].preid << ' ' <<node[i].nextid << endl;
// }
// }
// cout << "============================" << endl;
int M;
scanf("%d", &M);
for(int i = 0; i < M; i++) {
int x;
scanf("%d",&x);
if(node[x].val) { //删除,修改两处
node[x].val = 0;
int prenode = node[x].preid;
int nextnode = node[x].nextid;
node[prenode].nextid = node[x].nextid;
node[nextnode].preid = node[x].preid;
}
}
int head = 0;
for(int i = 1; i <= N; i++) {
int prenode = node[i].preid;
if(node[i].val && !node[prenode].val) {
head = i;
break;
}
}
// for(int i = 1; i <= N; i++) {
// if(node[i].val) {
// cout << i << ' ' << node[i].preid << ' ' << node[i].nextid << endl;
// }
// }
// cout << "-------------------------" << endl;
for(int i = 1; i <= N; i++) {
if(!head) break;
cout << head << ' ';
head = node[head].nextid;
}
return 0;
}
第一次写错误
对用结构体数组实现链表不熟练,