题目
给定一个常数 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 (≤10 ^ 5)、以及正整数 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
思路
终于碰到有些难度的题目了。
用map存储该模拟链表,以做到由地址直达节点。
确实建立一个模拟单链表,逐段手工反转指针(尝试过建立成双向链表,节点中加入pre字段,感觉有些取巧了)。
反转每段单向链表时,入栈存储节点地址,出栈替换next指针。记录上一段链表的头,当遍历到下一段链表的尾时,将二者连起来。
需要注意的坑:
-
测试点5考察大规模数据量,输入输出用cin cout就会超时。原本考虑用string存储地址,但scanf和printf都不支持直接读入或输出string(都需要额外转换操作),故转而都改成int,输出如00100的数字时用控制格式输出。
-
测试点6考察“存在无效节点”的情况,真是挖的一手好坑。需要重新遍历一遍,计算有效节点的数量。
算法
#include <iostream>
#include <stack>
#include <map>
using namespace std;
struct Node{
int addr;
int data;
int next;
};
int main()
{
int head, n, k;
scanf("%d %d %d", &head, &n, &k);
map<int, Node> nodeMap;
for (int i=0; i<n; i++){
Node *node = new Node();
scanf("%d %d %d", &node->addr, &node->data, &node->next);
nodeMap[node->addr] = *node;
}
//注意坑!并非所有节点都有效,需重新数有效节点数
n = 0;
for (int h=head; h!=-1; h=nodeMap[h].next){
n++;
}
int len = n;
int curHead = head;
int preHead = -1;
while (len >= k){
int p = curHead;
stack<int> s;
//p从头遍历到尾
for (int i=0; i<k-1; i++){
s.push(p);
p = nodeMap[p].next;
}
//若是第一组,将其尾作为新的总的头
if (len==n){
head = p;
}
//关联上一组的头和当前组的尾
if (preHead!=-1){
nodeMap[preHead].next = p;
}
//curHead指向下一组
preHead = curHead;
curHead = nodeMap[p].next;
//反转链表
for (int i=0; i<k-1; i++){
nodeMap[p].next = s.top();
p = s.top();
s.pop();
}
len -= k;
}
nodeMap[preHead].next = curHead;
int p = head;
for (int i=0; i< n-1; i++){
printf("%05d %d %05d\n", nodeMap[p].addr, nodeMap[p].data, nodeMap[p].next);
p = nodeMap[p].next;
}
printf("%05d %d -1\n", nodeMap[p].addr, nodeMap[p].data);
return 0;
}