一、题目
给定一个常数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
二、个人理解
本题主要考察对数组的反转问题。个人思路为:定义结构体数组,进行逐一的反转。
关键点:
- 如何得到顺序的节点连接。题目中给出的是无序,所以我们要进行转换,一开始我使用两个for循环,但是测试点运行超时,故后来改成用map(数组也可以)进行排序。
- 节点的不断更新。我一开始是从前往后反转,最后的到18分,后来发现这样只有第一轮是正确的,当反转需要很多轮时,上一轮与下一轮经过反转后需进行连接处的更新,故我从后往前反转。
- 无效节点的问题。当我得到24分时,我就知道肯定有个特殊的,果然测试点有开始地址不是头节点的时候,故我们需考虑这一点。
正常版(C++):
#include <iostream>
#include <stdio.h>
#include <map>
using namespace std;
typedef struct node {
int paddress;
int naddress;
int data;
} linklist;
int main()
{
map<int, linklist> mp;
int first_address, num, k;
cin >> first_address >> num >> k;
linklist sort_node[num], result[num], node;
int addr, data, next;
//赋值,并将其插入到map中,key 为地址,值为节点
for (int i = 0; i < num; i++) {
cin >> addr >> data >> next;
node.paddress = addr;
node.data = data;
node.naddress = next;
mp.insert(pair<int, linklist>(addr, node));
}
//遍历,得出节点的顺序连接,注意结束节点的下一地址为-1,且总长度可能不为num
int size = 0;
while (first_address != -1) {
sort_node[size] = mp[first_address];
first_address = sort_node[size].naddress;
size++;
}
//复制
for (int i = 0; i < size; i++) {
result[i] = sort_node[i];
}
//总共需几轮反转
int total = size / k;
//从后往前开始反转(不包括不能反转的)
while (total >= 1) {
int j = total * k - 1;
if (total < size / k) {
sort_node[j].naddress = result[j + 1].paddress;//完成一次反转后,进行这次反转的开头地址与即将开始的一轮反转的尾地址更新
}
first_address = sort_node[j].paddress;//取出一轮反转的首地址
for (int i = (total - 1) * k; i < k * total; i++) {
result[i].paddress = first_address;
result[i].data = sort_node[j].data;
if (j == (total - 1) * k) {
result[i].naddress = sort_node[total * k - 1].naddress;//当进行到反转最后一个时,后地址为原来结点的下地址
break;
}
result[i].naddress = sort_node[j - 1].paddress;
first_address = result[i].naddress;//记录节点的下一地址,为即将反转的节点服务
j--;
}
total--;
}
//输出,注意最后一个的输出
for (int i = 0; i < size - 1; i++) {
printf("%05d %d %05d\n", result[i].paddress, result[i].data, result[i].naddress);
}
printf("%05d %d %d\n", result[size - 1].paddress, result[size - 1].data, result[size - 1].naddress);
}
关于这一个版本,我只能说我看问题的角度可能过于死板,非要把所有的东西绑在一起,那样太麻烦了。
下一版的主要算法为:对题目分析得出,其实每个节点的后地址就是下一地址的前地址,故我们只需要每个地址的前地址就ok了(而尾部地址一直为-1)。可以将前地址就当作每一节点的地址,然后创建一个数据数组,下标为前地址,值为后地址,另外一个地址数组,存放后地址。然后进行节点的连接,实际为地址的连接,并存放到list数组。直接对地址进行反转(实际就是对节点进行反转),这里就不需要考虑更新问题(因为其中根本没包括后地址)。最后输出时注意后地址就是下一节点的前地址。
超神版(转载):
#include <iostream>
#include <algorithm>
#include <stdio.h>
using namespace std;
int main()
{
int first, k, n, temp;
cin >> first >> n >> k;
int data[100005], next[100005], list[100005];
for (int i = 0; i < n; i++) {
cin >> temp;
cin >> data[temp] >> next[temp];
}
int sum = 0;//不一定所有的输入的结点都是有用的,加个计数器
//list对应顺序的节点(以地址表示)
while (first != -1) {
list[sum++] = first;
first = next[first];
}
//直接对地址反转,本质上为对节点反转
for (int i = 0; i < (sum - sum % k); i += k) {
reverse(list + i * k, list + i * k + k);
}
//注意输出
for (int i = 0; i < sum - 1; i++) {
printf("%05d %d %05d\n", list[i], data[list[i]], list[i + 1]);
}
printf("%05d %d -1", list[sum - 1], data[list[sum - 1]]);
return 0;
}