PTA basic 1025 反转链表 (25 分) c语言实现(gcc)
给定一个常数 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 (≤)、以及正整数 K (≤),即要求反转的子链结点的个数。结点的地址是 5 位非负整数,NULL 地址用 − 表示。
接下来有 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
核心思路图解
初步思路声明链表,然后按照头插法插入结点,当index+k<=n的时候,原地翻转index到index+k-1的结点,temp指针指向index的指针指向
提交后检测点5超时,显然是给出了极限条件,即当数据足够多的时候,遍历算法的效率急速下降,所要用到链表和指针来解决问题
遍历的方法会超时,用静态结构数组存储数据,使用下标作为指针模拟链表,进行链表的局部翻转,然后输出翻转后的结果
关于局部翻转的算法
核心思想
首先用三个指针来记录翻转前一个结点地址pre,后一个结点地址next以及当前节点地址current,然后就是处理子链首尾的连接
因为翻转后链表的首地址会发生变化,因此需要单独记录链表的首地址,这里用一次单独的循环来记录,结束后产生的子链首地址就是链表的首地址
题目给出的测试用例中,如果剩余子链不够k个,就不需要翻转,所以还要考虑到剩余结点的连接
第一次提交测试034通过,百度看别人写的代码 1.所给数据可能存在无效结点(测试点6通过)
第二次提交 使用静态链表方式重写了代码,测试点0246通过
第三次提交 制作了图解的链表算法过程 并且修正了多个子链表翻转时候的边界条件,检测点023456通过
最后一次提交通过
检测点1正好有N=m*K个子串需要翻转
检测点0 2 4 6 只有有1个子串,且N>K需要翻转的时候
检测点 3 5 有m个子串需要翻转且N>m*K
测试用例1
00100 12 4
00000 4 99999
00100 1 12309
68237 6 34257
33218 3 00000
99999 5 68237
12309 2 33218
34257 7 12922
12922 8 98122
98122 9 13812
13812 10 23721
23721 11 12371
12371 12 -1
测试用例2
00100 12 4
00000 4 99999
00100 1 12309
68237 6 34257
33218 3 00000
99999 5 68237
12309 2 33218
34257 7 12922
12922 8 98122
98122 9 13812
13812 10 23721
23721 11 12371
12371 12 18271
18271 13 -1
#include "stdio.h" #include "stdlib.h" #include "string.h" typedef struct protoTypeNode{ //地址直接用数组下标存储 ,使用的是静态结构体数组,原理和链表一样,但是题目已经给定了数据的地址,没办法使用动态链表来存储固定地址尤其是地址为0的数据, int data; int next; } node; //翻转链表核心算法 int reverse(node *linkListPointer,int head,int n,int k){ node *lp=linkListPointer;//链表指针 int pre=0,current=0,next=0,nexthead,j=0,rear=0,repeatTimes,i; //pre用于记录需要翻转的结点的前一个结点地址 //current当前翻转结点的地址 //next下一个需要翻转结点的地址 //nexthead下一个子链的首结点 //rear子链翻转后的尾结点(即未翻转的子链的头结点) //repeatTime有多少个子链需要翻转 //remainList需要翻转的子链全部翻转后是否还剩下结点 pre=head;//指向未翻转的子链表的头结点 current=lp[head].next;//指向头结点的下一跳结点,同时也是准备翻转的结点,准备用于翻转指向原局部头结点 next=lp[current].next;//保存翻转结点的下一跳结点 for(i=0;i<k-1;i++){//第一个元素的下一跳指针需要单独处理,所以重复k-1次 lp[current].next=pre;//前一个元素的地址pre存入current指向的结点的下一跳指针中 pre=current;//pre current next前移一位 current=next; next=lp[next].next; } rear=head;//原局部头结点成为翻转后的尾结点 head=pre;//局部翻转后,生成新的链表表头结点 //翻转剩下的子链 repeatTimes=n/k;//需要翻转多少个子链 for(j=0;j<repeatTimes-1;j++){ nexthead=pre=current;//更新子链头结点 //pre current next前移一位 current=lp[nexthead].next; next=lp[current].next; for(i=0;i<k-1;i++){ lp[current].next=pre;//将前一个结点的地址存入当前翻转节点的next指针中 pre=current;//pre current next前移一位 current=next; next=lp[next].next; } lp[rear].next=pre;//使 上一个翻转子链表的尾结点的指针指向当前 翻转子链表的新头结点 rear=nexthead;//当前子链表的局部头结点成为翻转后的尾结点 } lp[rear].next=current;//翻转后的子链尾结点指向剩余子链的首结点 return head; } //统计有效结点 int countList(node *lp,int head){ int count=0,current=head; while(lp[current].next!=-1){ current=lp[current].next; count++; } return count+1; } //按序打印链表 void printList(node *lp,int head,int count){ int current=head,i; // printf("\n"); for(i=0;i<count-1;i++){ printf("%05d %d %05d\n",current,lp[current].data,lp[current].next); current=lp[current].next; } printf("%05d %d %d\n",current,lp[current].data,lp[current].next); } int main(){ int head=0,i,n=0,k=0,count,address,data,next,newhead; scanf("%d %d %d",&head,&n,&k); node linkList[100000];//N<=10^5 for(i=0;i<n;i++){ scanf("%d %d %d",&address,&data,&next); linkList[address].data=data; linkList[address].next=next; } count=countList(linkList,head); newhead=reverse(linkList, head, count, k); printList(linkList, newhead, count); return 0; }
后记.
说实话,以前只是知道链表的逻辑,但是手动写代码出来建立一个链表来实现某种算法一直都不会,花了挺长时间一步步debug才搞懂
其实翻转子链的地方并不难理解, 难点是边界处理和子链的链接问题
包括 链表的首地址更新,上一个子链的尾结点和下一个子链的头结点的链接, 更新头尾结点