問題描述
Given a constant K and a singly linked list L, you are supposed to reverse the links of every K elements on L. For example, given L being 1→2→3→4→5→6, if K=3, then you must output 3→2→1→6→5→4; if K=4, you must output 4→3→2→1→5→6.
Input Specification:
Each input file contains one test case. For each case, the first line contains the address of the first node, a positive N (≤105?? ) which is the total number of nodes, and a positive K (≤N) which is the length of the sublist to be reversed. The address of a node is a 5-digit nonnegative integer, and NULL is represented by -1.
Then N lines follow, each describes a node in the format:
Address Data Next
where Address is the position of the node, Data is an integer, and Next is the position of the next node.
Output Specification:
For each case, output the resulting ordered linked list. Each node occupies a line, and is printed in the same format as in the input.
Sample Input:
00100 6 4
00000 4 99999
00100 1 12309
68237 6 -1
33218 3 00000
99999 5 68237
12309 2 33218
Sample Output:
00000 4 33218
33218 3 12309
12309 2 00100
00100 1 99999
99999 5 68237
68237 6 -1
題意便是把鏈表的每K個節(jié)點(diǎn)反轉(zhuǎn),如果最后結(jié)尾的節(jié)點(diǎn)數(shù)小于K,便不需要反轉(zhuǎn)
頭節(jié)點(diǎn)格式:
[第一個數(shù)據(jù)的節(jié)點(diǎn)地址 數(shù)據(jù)節(jié)點(diǎn)數(shù) 反轉(zhuǎn)數(shù)K]
[address N K]
其他節(jié)點(diǎn)內(nèi)容的格式為:
[本節(jié)點(diǎn)地址 數(shù)據(jù) 下一個節(jié)點(diǎn)地址]
[address data next]
Notice:如果下一個節(jié)點(diǎn)地址(next)為-1相當(dāng)于null,表示這是最后一個節(jié)點(diǎn)
具體如圖所示:
由上方鏈表轉(zhuǎn)為下方鏈表(K == 3)

解決思路
Tip:后面所說的函數(shù)都是代碼里面的函數(shù)
正文:
由示例可知,題目給出數(shù)據(jù)時(shí),節(jié)點(diǎn)是雜亂無章的。首先做的第一件事便是將之排序成為一個有序的鏈表。
這一步我的思路便是使用一個鏈表來接受這些數(shù)據(jù),然后把它們排序之后放入一個隊(duì)列K
使用createList() 把題目數(shù)據(jù)一股腦放到一個鏈表之中(無序)
使用putListToQueue()將那些數(shù)據(jù)按照地址順序排好放在一個隊(duì)列K之中
這里可能會有一個疑問,這里為什么使用隊(duì)列?直接在鏈表上完成整個操作不可以嗎?
直接在鏈表操作確實(shí)可以,但是!其中涉及Node的排序,反轉(zhuǎn)的操作便會復(fù)雜些!而鏈表反轉(zhuǎn)是符合棧(First in Last Out)的特性的!而且最后一個測試點(diǎn)還有一個坑?。。ê竺嬖僬f)使用鏈表個人會覺得變得麻煩許多,不利于思維的通暢
使用隊(duì)列我們稍微改造一番也可以讓隊(duì)列具有棧的特性。而按順序輸出便完全符合隊(duì)列的特性(First in Last out),所以只使用隊(duì)列完全可以符合我們的反轉(zhuǎn),輸出要求
好了,現(xiàn)在我們已經(jīng)得到一個排序好的隊(duì)列K了
下一步我們只需使reserveQueue()將隊(duì)列K反轉(zhuǎn)到另一個隊(duì)列Q,然后把隊(duì)列Q輸出即可
Notice!使用reserveQueue()是把隊(duì)列K元素pop到隊(duì)列Q來完成反轉(zhuǎn)的的!
也就是說我們把隊(duì)列K每K個元素以棧的方式pop到隊(duì)列Q之中,從而得到了一個已經(jīng)反轉(zhuǎn)好的隊(duì)列!
還有一點(diǎn)!把隊(duì)列K我們只是把每K個節(jié)點(diǎn)用棧的方式輸出,但是我們把每K個節(jié)點(diǎn)(結(jié)尾不管夠不夠K個都算是一個節(jié)點(diǎn))看成一個大的節(jié)點(diǎn)是按照隊(duì)列的方式輸出的!也就是說,局部以棧的方式pop,整體以隊(duì)列的方式pop
最后使用printResult()將結(jié)果輸出即可
Some Detail Of Function (里面有一些寫題時(shí)的思路,有助于理解代碼,便于體會
這里將講解一些題目中的難點(diǎn),以及實(shí)現(xiàn)該操作的函數(shù)的一些細(xì)節(jié)(建議結(jié)合代碼一起看
createList:將數(shù)據(jù)放入鏈表,基本沒什么坑,略
printResult:把隊(duì)列內(nèi)容輸出,基本也沒什么坑,唯一要注意的是最后一個節(jié)點(diǎn)的下一個地址是-1,不能以%05的方式輸出,略
putListToQueue:作用是排序鏈表并將之傳入隊(duì)列K
我們傳入初始地址(是頭節(jié)點(diǎn)中包含的信息,第一個節(jié)點(diǎn)的地址),每次通過地址找到對應(yīng)的節(jié)點(diǎn),然后更新一下地址為該節(jié)點(diǎn)的next,再通過address尋找下一個節(jié)點(diǎn),直到排序完整個鏈表。
這里需要說明一下地址:我們通過地址尋找節(jié)點(diǎn),不是使用結(jié)構(gòu)體的計(jì)算機(jī)地址,是格式中的address!,結(jié)構(gòu)體的的計(jì)算機(jī)地址只是我們訪問各個節(jié)點(diǎn)的橋梁
這里分享一下寫這道題的有關(guān)這個函數(shù)的思路:
在代碼中我使用了head->nextNode != 0作為for循環(huán)的中止條件,后面補(bǔ)了address != -1的終止條件。
之所以補(bǔ)了這個條件,便是因?yàn)?strong>測試點(diǎn)6的原因。測試點(diǎn)6導(dǎo)致我被卡了好久啊啊?。。。?/p>
測試點(diǎn)6的意思便是,給出的數(shù)據(jù)有些Node是孤立的!也就是說,最后輸出的結(jié)點(diǎn)數(shù)和給的結(jié)點(diǎn)數(shù)是不一樣的?。?/strong>這就導(dǎo)致了K有可能是大于N的?。?!
這個測試點(diǎn)坑了我好久orz,我之前以為給出的數(shù)據(jù)會排出一個和原來結(jié)點(diǎn)數(shù)一樣的鏈表(所以head->nextNode != 0作為for循環(huán)的中止條件)
因?yàn)闇y試點(diǎn)6 補(bǔ)了一個address != -1的終止條件,因?yàn)楫?dāng)address被更新到了-1表示已經(jīng)排序完成。雖然本來就可以用這個條件作為終止條件,但是沒有想到會出現(xiàn)孤立點(diǎn),便沒有使用
Notice:這里只是按照地址,排列好了鏈表
reserveQueue:將數(shù)據(jù)反轉(zhuǎn),并且連接地址
反轉(zhuǎn):每次rear前進(jìn)K個元素,然后用一個變量等于rear-1,進(jìn)行棧的pop來反轉(zhuǎn),length表示剩余結(jié)點(diǎn)數(shù),如果小于K了,便直接順序輸出便可
Notice:
1.這里反轉(zhuǎn)好了鏈表但是地址可能打亂了
2.反轉(zhuǎn)好的元素在隊(duì)列里
連接地址:如果K == 1,無需連接直接return,因?yàn)榈刂繁旧砭褪桥藕玫?/p>
當(dāng)K != 1直接把下一個元素的地址賦給上一個元素的next即可
不過這一塊我寫復(fù)雜了,是因?yàn)楫?dāng)時(shí)沒考慮到M%K = 0的情況,直接以next->node[temp]->next != -1作為終止條件,是因?yàn)楫?dāng)時(shí)偷懶不想直接使用** i < lengths**作為遍歷條件,而直接以找到-1作為終止條件,當(dāng)M%K = 0會造成一部分元素沒有連接。
而i < lengths這個條件兩個情況都適配,只連接M-1個節(jié)點(diǎn),最后一個節(jié)點(diǎn)的next直接寫-1,但是我懶得改了,希望沒有讓你們感到誤解,困惑

Code
#include<stdio.h>
#include<malloc.h>
#define size 100000
typedef struct node {
int address;
int data;
int next;
struct node* nextNode;
}Node, *Pnode;
typedef struct queue {
Pnode node[size+1];
int front;
int rear;
}Queue,*Pqueue;
Pnode createList(int lenght);
void putListToQueue(Pnode head, Pqueue queue, int address);
void reserveQueue(Pqueue first, Pqueue next, int reserveNumber, int length);
void printResult(Pqueue queue);
int main(void) {
Node iniInformation;
int lenght;//數(shù)列長度
int reserveNumber;//反轉(zhuǎn)間隔
int nextAddress;//address of next node
Pnode head = 0;
Pqueue queueResult = (Pqueue)malloc(sizeof(Queue));
Pqueue temp = (Pqueue)malloc(sizeof(Queue));
scanf("%d %d %d", &iniInformation.address, &iniInformation.data, &iniInformation.next);
lenght = iniInformation.data;
reserveNumber = iniInformation.next;
nextAddress = iniInformation.address;
//初始化隊(duì)列的front and rear
temp->front = temp->rear = 0;
queueResult->front = queueResult->rear = 0;
//create list
head = createList(lenght);
//排序數(shù)列
putListToQueue(head, temp,nextAddress);
//反轉(zhuǎn)數(shù)列
reserveQueue(temp, queueResult, reserveNumber, temp->front - temp->rear);
//output result
printResult(queueResult);
return 0;
}
Pnode createList(int lenght) {
Pnode temp = 0;
Pnode head = temp = (Pnode)malloc(sizeof(Node));
for (int i = 0; i < lenght; i++) {
Pnode node = (Pnode)malloc(sizeof(Node));
scanf("%d %d %d", &node->address, &node->data, &node->next);
temp->nextNode = node;
temp = node;
temp->nextNode = 0;
}
return head;
}
void printResult(Pqueue queue) {
Pnode node = 0;
for (;queue->rear != queue->front;) {
node = queue->node[queue->rear];
if (node->next != -1) {
printf("%0.5d %d %0.5d\n", node->address, node->data, node->next);
}else{
printf("%0.5d %d %d", node->address, node->data, node->next);
}
queue->rear++;
}
}
void putListToQueue(Pnode head, Pqueue queue, int address) {
Pnode temp = 0;
for (; head->nextNode != 0;) {
temp = head;
for (;temp->nextNode != 0 && temp->nextNode->address != address;) {
temp = temp->nextNode;
}
address = temp->nextNode->next;
queue->node[queue->front++] = temp->nextNode;
temp->nextNode = temp->nextNode->nextNode;
if (address == -1) {
break;
}
}
}
void reserveQueue(Pqueue first, Pqueue next, int reserveNumber, int length) {
int temp;
int lengths = length;
for (; length > 0;) {
if (length >= reserveNumber) {
first->rear += reserveNumber;
temp = first->rear - 1;
for (int i = 0; i < reserveNumber; i++) {
next->node[next->front++] = first->node[temp--];
}
}else{
for (;first->rear != first->front;) {
next->node[next->front++] = first->node[first->rear++];
}
}
length -= reserveNumber;
}
if (reserveNumber == 1) {
return;
}
temp = next->rear;
if (lengths % reserveNumber == 0)
{
for (int i = 1; i < lengths; i++, temp++)
{
next->node[temp]->next = next->node[temp + 1]->address;
}
next->node[temp]->next = -1;
}else{
for (; next->node[temp]->next != -1; temp++) {
next->node[temp]->next = next->node[temp + 1]->address;
}
}
}
Summary
這道題還是很有難度的,尤其是測試點(diǎn)6!?。懥诉@題總體給我的感覺就是我還需要加強(qiáng)考慮各種漏洞(特殊情況)
像這次便導(dǎo)致我會有很多地方會有打補(bǔ)丁式的代碼,便如我前面的講解思路一樣,putListToQueue() and reserveQueue()加了注水代碼,只需要一個條件便可以適配,卻多加了一個 if-else
雖然這個看起來挺復(fù)雜的(不過我感覺用鏈表更麻煩,反正我是不想怎么實(shí)現(xiàn)了!還有個使用三個數(shù)組來實(shí)現(xiàn)的,僅僅用了三十多行代碼,有興趣可以看一看https://blog.csdn.net/liyuanyue2017/article/details/83269991
)
不過利用了隊(duì)列,棧,鏈表來實(shí)現(xiàn)做法,使得算法快了許多,因?yàn)樵诳磩e人的測試時(shí)間大多數(shù)都是100-200ms,這個基本在50-60ms
不過寫完了這個博客突然又覺得簡單好多(才怪),可能是寫了博客自己總結(jié)一番的原因hhh