说明
这题不想清楚还是有很多坑的,改了两次才正确,第一次3、4测试点timeout,因为用了两个循环, O ( n 2 ) O(n^2) O(n2)的算法。第二次多余结点处理错误,最后结束输出-1这个弄了半天。总的来说,这题算细节题,边界的细节处理真的要命。
本程序保证正确:
题目来源:
7-3 重排链表
分数 25
作者 陈越
单位 浙江大学
题目
给定一个单链表 L 1 L_1 L1→ L 2 L_2 L2→⋯→ L n − 1 L_{n-1} Ln−1→_L_ n _n n,请编写程序将链表重新排列为 L n → L 1 → L n − 1 → L 2 → ⋯ L_n→L_1→L_{n−1}→L_2→⋯ Ln→L1→Ln−1→L2→⋯。例如:给定_L_为1→2→3→4→5→6,则输出应该为6→1→5→2→4→3。
输入格式:
每个输入包含1个测试用例。每个测试用例第1行给出第1个结点的地址和结点总个数,即正整数
N
≤
1
0
5
N≤10^5
N≤105。结点的地址是5位非负整数,NULL地址用−1表示。
接下来有_N_行,每行格式为:
Address Data Next
其中Address是结点地址;Data是该结点保存的数据,为不超过
1
0
5
10^5
105的正整数;Next是下一结点的地址。题目保证给出的链表上至少有两个结点。
输出格式:
对每个测试用例,顺序输出重排后的结果链表,其上每个结点占一行,格式与输入相同。
输入样例:
00100 6
00000 4 99999
00100 1 12309
68237 6 -1
33218 3 00000
99999 5 68237
12309 2 33218
输出样例:
68237 6 00100
00100 1 99999
99999 5 12309
12309 2 00000
00000 4 33218
33218 3 -1
代码长度限制
16 KB
时间限制
500 ms
内存限制
64 MB
思路
- 总的思路
思考几个问题:怎么存数据?怎么重排链表?怎么输出结果(即遍历数据)?
- 因为涉及到头和尾的频繁操作,想到用双向链表存储;
- 重排链表,em…我想到最简单的就是在读取的时候按照题目要求的顺序读取就好了;我的思路是每次读取两个结点,tail和head,先尾后头,然后每次删除head和tail(就是head后移,tail前移)。具体可以结合代码看看。
- 那么输出结果的方法就不用太考虑了,主要就是结尾输出-1,就是判断什么时候结束的问题了。
- 定义链表结构体 Node:
主要就是添加了一个前驱结点。这里用string
来存地址,省的格式化的时候麻烦。
然后就是用unordered_map<string, Node> nodes
来模拟内存,存储这些数据。
当前结点地址值就是这个map的key。
struct Node {
string addr; // 节点地址
int data; // 节点数据
string next; // 下一个节点地址
string pre; // 前驱指针
};
unordered_map<string, Node> nodes;
- 读取链表与构造双向指针:
- 先按照输入格式,读取单向链表。
for (int i = 0; i < N; ++i) {
Node node;
cin >> node.addr >> node.data >> node.next;
nodes[node.addr] = node;
}
- 找到尾结点,在这过程中设置前驱指针。主要就是用快慢指针的方法,记录前一个结点的地址。
nodes[head].pre = "-1";
// 找到尾结点,并添加前驱指针
while (nodes[cur].next != "-1") {
tail = cur;
cur = nodes[cur].next;
nodes[cur].pre = nodes[tail].addr;
}
- 先尾后头输出结果
先说明,cur为本轮的末尾结点,就是待删除结点,tail是删除cur后的尾结点,是下一轮循环的尾结点。head就是当轮循环的头结点。后面要是思绪乱了,把这三个变量内涵再想一想。
这里是最麻烦的。要考虑什么时候结束?(因为可能存在多余结点)结束后最后一个输出的时-1的问题。同时怎么移动head和tail两个指针也是关键。
关键点是理解if (nodes[head].next == "-1")
行head的下一个为-1时结束了,因为前面有 nodes[tail].next = "-1";
这段代码,tail是本次循环结束后下一循环的尾部,tail如果和head相等了,那就说明全部都遍历过了。因此if (nodes[head].next == "-1")
等价于if (head == tail)
。
但是还没结束,如果链表是奇数个怎么办?所以又要添加下面这段代码,当cur和head
if (nodes[cur].addr == nodes[head].addr) {
cout << nodes[cur].addr << " " << nodes[cur].data << " " << "-1";
break;
}
这一步的代码就是:
for (int i = 0; i < N / 2; ++i) {
// 先尾后头,找到尾结点
cout << nodes[cur].addr << " " << nodes[cur].data << " " << nodes[head].addr << endl;
nodes[tail].next = "-1"; // 删除尾结点
cout << nodes[head].addr << " " << nodes[head].data << " ";
// 结束后要输出-1
if (nodes[head].next == "-1") {
cout << "-1";
break;
}
else {
cout << nodes[tail].addr << endl;
}
head = nodes[head].next; //删除头结点
// 刷新cur和tail结点
cur = tail;
tail = nodes[tail].pre;
if (nodes[cur].addr == nodes[head].addr) {
cout << nodes[cur].addr << " " << nodes[cur].data << " " << "-1";
break;
}
}
源代码
#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;
struct Node {
string addr;
int data;
string next;
string pre; // 添加前驱指针
};
int main() {
string head;
cin >> head; // 头指针
int N;
cin >> N;
string tail=head; // 尾指针
string cur = head; // 移动指针
unordered_map<string, Node> nodes;
for (int i = 0; i < N; ++i) {
Node node;
cin >> node.addr >> node.data >> node.next;
nodes[node.addr] = node;
}
nodes[head].pre = "-1";
// 找到尾结点,并添加前驱指针
while (nodes[cur].next != "-1") {
tail = cur;
cur = nodes[cur].next;
nodes[cur].pre = nodes[tail].addr;
}
for (int i = 0; i < N/2; ++i) {
// 先尾后头,找到尾结点
cout << nodes[cur].addr << " " << nodes[cur].data << " " << nodes[head].addr << endl;
nodes[tail].next = "-1"; // 删除尾结点
cout << nodes[head].addr << " " << nodes[head].data << " ";
// 结束后要输出-1
if (nodes[head].next == "-1") {
cout << "-1";
break;
}
else {
cout << nodes[tail].addr << endl;
}
head = nodes[head].next; //删除头结点
// 刷新cur和tail结点
cur = tail;
tail = nodes[tail].pre;
if (nodes[cur].addr == nodes[head].addr) {
cout << nodes[cur].addr << " " << nodes[cur].data << " " << "-1";
break;
}
}
return 0;
}