PAT 乙级练习 1025 反转链表
PAT 乙级练习 题解合集
题目
给定一个常数 K 以及一个单链表 L,请编写程序将 L 中每 K 个结点反转。例如:给定 L 为 1→2→3→4→5→6,K 为 3,则输出应该为 3→2→1→6→5→4;如果 K 为 4,则输出应该为 4→3→2→1→5→6,即最后不到 K 个元素不反转。
输入格式:
每个输入包含 1 个测试用例。每个测试用例第 1 行给出第 1 个结点的地址、结点总个数正整数 N (≤105 )、以及正整数 K (≤N),即要求反转的子链结点的个数。结点的地址是 5 位非负整数,NULL 地址用 −1 表示。
接下来有 N 行,每行格式为:
Address Data Next
其中 Address 是结点地址,Data 是该结点保存的整数数据,Next 是下一结点的地址。
输出格式:
对每个测试用例,顺序输出反转后的链表,其上每个结点占一行,格式与输入相同。
输入样例:
00100 6 4
00000 4 99999
00100 1 12309
68237 6 -1
33218 3 00000
99999 5 68237
12309 2 33218
输出样例:
00000 4 33218
33218 3 12309
12309 2 00100
00100 1 99999
99999 5 68237
68237 6 -1
思路
写在前面的废话:
第一次做这种给出地址的链表题很不习惯,看完题目,链表?反转?这不刚学完数据结构吗哈哈哈,头插法搞起来…于是写完反转函数一跑,指针都飞到天上去了…硬着头皮把 bug 都修完了去看了柳神的思路,顿时感觉自己把路走窄了。于是领会了一下核心思想,自己又重写了一版,虽然做不到那么精炼,但还是写一下自己的步骤吧。
步骤:
- 开一个长度
100010
的大数组nodes
接收所有结点,用head
记录头结点地址; - 从
head
开始遍历一遍链表,把结点的地址按照顺序存放在数组linklist
里; - 在数组
linklist
里按照题意把整个链表反转好; - 根据
linklist
里此时的顺序输出结点(注意这个时候不需要做额外的事情了,第i
个结点的地址就是linklist[i]
,数据是nodes[linklist[i]].data
,下一个结点的地址是linklist[i+1]
)
坑点:
- 不是所有给的结点都在链表里,可能存在没有用的结点;
- 所有地址,除了最后的
-1
,都有前导 0。
代码
在数组里反转
#include <stdio.h>
#define MAX 100010
typedef struct {
int data, next;
} Node;
Node nodes[MAX]; // 接收输入
int linklist[MAX]; // 存放所有链表结点的地址
void reverse(int *a, int len) {
int i;
for (i = 0; i < len / 2; ++i) {
int temp = a[i];
a[i] = a[len - i - 1];
a[len - i - 1] = temp;
}
}
int main() {
int head, n, k, i, add;
scanf("%d %d %d", &head, &n, &k);
for (i = 0; i < n; ++i) {
scanf("%d", &add);
scanf("%d %d", &nodes[add].data, &nodes[add].next);
}
int p = head, cnt = 0;
while (p != -1) {
linklist[cnt++] = p;
p = nodes[p].next;
}
int done = 0;
while (done + k <= cnt) {
reverse(linklist + done, k);
done += k;
}
for (i = 0; i < cnt; ++i) {
printf("%05d %d ", linklist[i], nodes[linklist[i]].data);
if (i == cnt - 1)
printf("-1\n");
else
printf("%05d\n", linklist[i + 1]);
}
return 0;
}
头插法(最早写的,感兴趣可以看看)
#include <stdio.h>
#define MAX 100010
typedef struct {
int data, next;
} Node;
Node nodes[MAX];
int count(int head) {
int cnt = 0, p = head;
while (p != -1) {
++cnt;
p = nodes[p].next;
}
return cnt;
}
// 修改 head 的后 1 个结点到 head 的后 len 个结点的 next
// 并且返回 head 的后 1 个结点的地址
// 例如对于 1 →2 →3 →4 →5 →6,reverse( 2 的地址, 3)后
// 链表变成 1 →2 →5 →4 →3 →6,并返回 3 的地址
int reverse(int head, int len) {
int p = nodes[head].next, q, i;
int ret = p;
nodes[head].next = -1;
for (i = 0; i < len; ++i) {
q = nodes[p].next;
nodes[p].next = nodes[head].next;
nodes[head].next = p;
p = q;
}
nodes[ret].next = p;
return ret;
}
void print(int head) {
int p = head;
while (p != -1) {
printf("%05d %d ", p, nodes[p].data);
if (nodes[p].next == -1)
printf("-1\n");
else
printf("%05d\n", nodes[p].next);
p = nodes[p].next;
}
}
int main() {
int head, n, k, i, add;
scanf("%d %d %d", &head, &n, &k);
for (i = 0; i < n; ++i) {
scanf("%d", &add);
scanf("%d %d", &nodes[add].data, &nodes[add].next);
}
int cnt = count(head); // 统计总结点个数
int done = 0; // 记录已经处理好的节点个数
nodes[100000] = {0, head}; // 造一个真正的头结点
int p = 100000; // 用 p 遍历链表
while (done + k <= cnt) {
p = reverse(p, k);
done += k;
}
print(nodes[100000].next);
return 0;
}