给定一个常数 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
第一次的代码:迭代,但是出现最后两个测试点段错误和答案错误;
/*
这里结点格式,选用两个字符串一个整型,因为这里地址5位整数,有可能不足5位,用
int输入不方便,直接用字符串输入,比较;然后就是反转,题目输入格式为首地址
结点个数,然后就是翻转结点数K;链表翻转时就是第一个不动,随后K-1个翻转,
然后变成了前K个结点逆序的链表,加后N-K个结点顺序的两个链表,如果K<N就需要将
后面的链表链接在前一个链表的后面;
例:结点及第一个结点地址:
00100
00000 4 99999
00100 1 12309
68237 6 -1
33218 3 00000
99999 5 68237
12309 2 33218
链表:
00100 1 12309
12309 2 33218
33218 3 00000
00000 4 99999
99999 5 68327
68327 6 -1 //这里末尾结点next指针指向空-1;
k=4,k<6;
翻转:
00100 1 12309 找下一个结点,12039
12309 2 33218 然后翻转,
这里第一个结点next变为空,00100 1 -1;
然后第二个结点指向上一个结点; 12309 2 00100
这里发现33218 没了,所以我们用一个next变量存放翻转结点的下一个结点地址
即next=33218;
然后就变成;
00100 1 -1
12309 2 00100 //这里翻转一次,已经是两个结点了;
然后根据next=33218找到下一个结点
00100 1 -1
12309 2 00100
33218 3 00000
这里进行翻转,先存放33218的下一个结点 next=00000;
然后00000变为12309,但是链表之间已经断了,3这个结点指向4而非指向2
所以应该用一个来存放之前的地址,pre=12309,00000=pre,于是变成:
00100 1 -1
12309 2 00100
33218 3 12309
然后到这里pre变成33218,移到下一个结点 根据next==00000找到00000 4 99999
00100 1 -1
12309 2 00100
33218 3 12309
00000 4 99999
根据pre==33218,更新next为99999,然后99999赋值为33218:
00100 1 -1
12309 2 00100
33218 3 12309
00000 4 33218
到这里翻转了4个结点,只需要迭代3次;剩下链表:
99999 5 68327
68327 6 -1
此时pre=00000,next=99999;
将翻转后链表按头结点排序,方便看:
00000 4 33218
33218 3 12309
12309 2 00100
00100 1 -1
------------
99999 5 68327
68327 6 -1
这里将我们一开始应该输入一个头结点地址,head,翻转之后,head的位置应该是末尾
然后将链表链接,head->Next=next,这样就变成00100 1 99999
于是新链表:
00000 4 33218
33218 3 12309
12309 2 00100
00100 1 99999
99999 5 68327
68327 6 -1
这样就是K<N的一种翻转过程;如果K==N;就不需要进行链表拼接这一步;直接全部翻转即可
补充:
这里注意是每K个翻转一次,每K个翻转一次;读题一半开写,以为是前K个翻转;这里就需要计数
了,这里;
看下标;
0123456789
0123456789
这里K=3时,2105438769
因为翻转只看链表后K-1个,就是从1到2,在2进行翻转之后就结束
0<-1<-2 3->4->5->6->7->8->9
这里输出210
然后从3开始,3布翻转,就是从4到4,在5进行翻转之后就结束
2105438769
所以这里需要一个变量标志后部分链表是否需要翻转;
每次局部翻转的边界是3 6 9;就是K*n,n=1,2,3,4;
0->1->2->3->4->5->6->7->8->9,k=3;
第一次翻转:
0<-1-<-2 3->4->5->6->7->8->9
这里翻转之后,0应该拼接到下一个翻转链表的头结点;
第二次翻转:
2->1->0 3<-4<-5 6->7->8->9
这里0应该指向第二个头结点5 于是:
2->1->0->5->4->3 6->7->8->9
第三次翻转:
2->1->0->5->4->3 6<-7<-8 9
这里3指向下一个链表的头结点;
2->1->0->5->4->3->8->7->6 9
然后这里9不足3个不用翻转;所以尾结点6指向9;
结果:
2->1->0->5->4->3->8->7->6->9;
变成这种形态需要移动数组,但是可以通过局部倒序输出实现,不改变数组;
但是移动数组也有一个优点,就是拼接的时候很方便;
然后看翻转过程;
1 2 3 4 5 6 7 8
0 1 2 3 4 5 6 7 k=3;
index从0开始,数3个,然后翻转后面2(k-1)个;
翻转之后index指向下标2;,下一次index++,让index指向下一个头结点下标3,
然后index+K-1就是满足可以翻转的下标,K-1就是头结点后面个;
如果index+K-1<=7,也就是在下标之内,就可以进行翻转;
index+K-1<=N-1;-->index+K<=N;
这里每次都时从index到index+K-1这样一组来的,当index==0时
就是0-2这k个;index==3,3-5这k个,边界为<index+K;
到这里为止都是用的迭代的方式实现反转,但是不知道为什么,最后两个测试点段错误和答案错误;
然后学习了别人的代码,局部反转,通过这个下标来实现next的功能,然后逆序通过下标对应
交换实现反转,很有创意的想法,在数组中确实时很方便的操作;
边界:
奇数时下标:012;中间不交换,于是是中间的前一个,一共K个,K/2==3/2==1,
01234 K=5,K/2==5/2==2;
这里就是下标小于K/2;
偶数时下标:01,无中间;K=2;K/2==2/2==1;也是小于K/2;
比如:
1 2 3 4 5 6 7
0 1 2 3 4 5 6
这里k=3;要反转的段数是N/K==7/3=2;反转前两段;
第一段;
这里用的是交换实现逆序,用一个变量存放第一次交换位置的数据;
temp=data(0);
data(0)=data(2);
data(2)=temp; //这里就实现了交换逆序;
然后这里前半段与后半段交换实现逆序,
第二段:
第二段起始位置是3;
temp=data(3);
data(3)=data(5);
data(5)=tmep;
起始位置下标就是,这里考虑到用两层循环,一层描述反转的段数;
一层描述段内的反转;反转段数循环变量暂定为i,内层循环为j;
那么反转开始的下标就应该就是 i*K+j;j每次从0开始;
两段:
第一段:i=0,j=0;
下标:0==i*K+j==0*3+0==0
第二段:i=1,j=0;
下标:3==i*K+j==1*3+0==3
这里i*K是外层段开始的位置,j是内部的循环;这是前半段的下标
然后看后半段的下标;这里i=0,j=0时,前段下标为0,后段下标为2
i=1,j=0时,前端下标为K==3,后段下标为5
段下标为K*(i+1)-1,然后j往后移动,那么后段往前,应该是-j;
后端下标:K*(i+1)-1-j;
K*i+K-1-j;
最后反转完成通过下标找到下一个,然后将地址赋值给next域,就实现了结点的链接;
这里升序的下标就是链表的暗链;因为这样翻转后的下一个结点一定是当前结点的后继
结点;
*/
#include<iostream>
#include<string>
using namespace std;
struct LNode{
string Address;
int Data;
string Next;
};
typedef struct LNode Node;
int main(){
//输入头结点地址,结点数N,以及翻转结点数K;
string head; //注意这一结点地址是字符类型;
int N,K;
cin>>head>>N>>K;
//输入结点信息;
struct LNode data[N];
for(int i=0;i<N;i++){
cin>>data[i].Address>>data[i].Data>>data[i].Next;
}
struct LNode List[N]; //存放链接后链表;
//构建链表;
string pre=head;
string next=head;
int index=0; //index指示下标;
while(next!="-1"){ //找到最后一个结点结束;最后一个结点的标志是Next域为“-1”;
for(int i=0;i<N;i++){ //查找与next地址相等的结点,
if(next==data[i].Address){
List[index++]=data[i]; //index为0时next指向head;
next=data[i].Next; //next指向当前结点的下一个;
break;
}
}
}
//翻转;
index=0; //index走到N-1;即走完链表结束;
int count=0; //翻转计数器,用计数器方便index操作;作为翻转的边界;
int thisList; //当前尾节点;下标;
while(index+K<=N){ //这里注意有一个等于,当index+K等于N时,下标为N-1;可翻转;
count=index+K; //翻转边界;
//局部翻转;翻转时index指向第一个;
pre=List[index].Address; //这里pre指向第一个结点;
next=List[index].Next; //然后从第二个开始翻转;
for(int i=index;i<count;i++){
if(next==List[i].Address){ //这里next一开始就指向index的下一个;
next=List[i].Next;
List[i].Next=pre;
pre=List[i].Address;
}
}
//完成本次翻转,我们尾结点指向应该指向下一个翻转的部分链表的头结点;
//或者指向不反转的末尾的头结点,或者是全翻转时改为-1;我们优先改为-1随后赋值;
//那么这里就需要一个变量存放本次翻转的尾结点地址,以及下次翻转后的头结点地址;
index+=K; //翻转结束后指向下一个链表头结点;
thisList=index-K; //存放上一个已翻转链表尾结点;
List[thisList].Next="-1"; //默认设置-1;
if(index+K<=N){ //如果后面还有需要翻转的部分;则将上一个链表与这个链表进行拼接;
List[thisList].Next=List[index+K-1].Address;
}
else if(index+K>N&&index<N){ //最后一部分不需要翻转;
List[thisList].Next=List[index].Address;
}
}
//输出;
//找到头结点向前输出,这是K<N的情况;
if(K<N){
//输出翻转的部分链表;
count=K; //输出的边界;
while(count<=N){
for(int i=count-1;i>=count-K;i--){
cout<<List[i].Address<<" "<<List[i].Data<<" "<<List[i].Next<<endl;
}
count+=K;
}
//输出剩下的链表;
for(int i=count-K;i<N;i++){
//这里i=count-K,因为上面while循环倒序在这里跳出;
//虽然没有进行输出,但是还是加了K,这里是顺序输出;
cout<<List[i].Address<<" "<<List[i].Data<<" "<<List[i].Next<<endl;
}
}
else if(N==K){ //完全翻转;
//全部倒序输出;
for(int i=N-1;i>=0;i--){
cout<<List[i].Address<<" "<<List[i].Data<<" "<<List[i].Next<<endl;
}
}
return 0;
}
第二次AC的代码:
/*
这里结点格式,选用两个字符串一个整型,因为这里地址5位整数,有可能不足5位,用
int输入不方便,直接用字符串输入,比较;然后就是反转,题目输入格式为首地址
结点个数,然后就是翻转结点数K;链表翻转时就是第一个不动,随后K-1个翻转,
然后变成了前K个结点逆序的链表,加后N-K个结点顺序的两个链表,如果K<N就需要将
后面的链表链接在前一个链表的后面;
例:结点及第一个结点地址:
00100
00000 4 99999
00100 1 12309
68237 6 -1
33218 3 00000
99999 5 68237
12309 2 33218
链表:
00100 1 12309
12309 2 33218
33218 3 00000
00000 4 99999
99999 5 68327
68327 6 -1 //这里末尾结点next指针指向空-1;
k=4,k<6;
翻转:
00100 1 12309 找下一个结点,12039
12309 2 33218 然后翻转,
这里第一个结点next变为空,00100 1 -1;
然后第二个结点指向上一个结点; 12309 2 00100
这里发现33218 没了,所以我们用一个next变量存放翻转结点的下一个结点地址
即next=33218;
然后就变成;
00100 1 -1
12309 2 00100 //这里翻转一次,已经是两个结点了;
然后根据next=33218找到下一个结点
00100 1 -1
12309 2 00100
33218 3 00000
这里进行翻转,先存放33218的下一个结点 next=00000;
然后00000变为12309,但是链表之间已经断了,3这个结点指向4而非指向2
所以应该用一个来存放之前的地址,pre=12309,00000=pre,于是变成:
00100 1 -1
12309 2 00100
33218 3 12309
然后到这里pre变成33218,移到下一个结点 根据next==00000找到00000 4 99999
00100 1 -1
12309 2 00100
33218 3 12309
00000 4 99999
根据pre==33218,更新next为99999,然后99999赋值为33218:
00100 1 -1
12309 2 00100
33218 3 12309
00000 4 33218
到这里翻转了4个结点,只需要迭代3次;剩下链表:
99999 5 68327
68327 6 -1
此时pre=00000,next=99999;
将翻转后链表按头结点排序,方便看:
00000 4 33218
33218 3 12309
12309 2 00100
00100 1 -1
------------
99999 5 68327
68327 6 -1
这里将我们一开始应该输入一个头结点地址,head,翻转之后,head的位置应该是末尾
然后将链表链接,head->Next=next,这样就变成00100 1 99999
于是新链表:
00000 4 33218
33218 3 12309
12309 2 00100
00100 1 99999
99999 5 68327
68327 6 -1
这样就是K<N的一种翻转过程;如果K==N;就不需要进行链表拼接这一步;直接全部翻转即可
补充:
这里注意是每K个翻转一次,每K个翻转一次;读题一半开写,以为是前K个翻转;这里就需要计数
了,这里;
看下标;
0123456789
0123456789
这里K=3时,2105438769
因为翻转只看链表后K-1个,就是从1到2,在2进行翻转之后就结束
0<-1<-2 3->4->5->6->7->8->9
这里输出210
然后从3开始,3布翻转,就是从4到4,在5进行翻转之后就结束
2105438769
所以这里需要一个变量标志后部分链表是否需要翻转;
每次局部翻转的边界是3 6 9;就是K*n,n=1,2,3,4;
0->1->2->3->4->5->6->7->8->9,k=3;
第一次翻转:
0<-1-<-2 3->4->5->6->7->8->9
这里翻转之后,0应该拼接到下一个翻转链表的头结点;
第二次翻转:
2->1->0 3<-4<-5 6->7->8->9
这里0应该指向第二个头结点5 于是:
2->1->0->5->4->3 6->7->8->9
第三次翻转:
2->1->0->5->4->3 6<-7<-8 9
这里3指向下一个链表的头结点;
2->1->0->5->4->3->8->7->6 9
然后这里9不足3个不用翻转;所以尾结点6指向9;
结果:
2->1->0->5->4->3->8->7->6->9;
变成这种形态需要移动数组,但是可以通过局部倒序输出实现,不改变数组;
但是移动数组也有一个优点,就是拼接的时候很方便;
然后看翻转过程;
1 2 3 4 5 6 7 8
0 1 2 3 4 5 6 7 k=3;
index从0开始,数3个,然后翻转后面2(k-1)个;
翻转之后index指向下标2;,下一次index++,让index指向下一个头结点下标3,
然后index+K-1就是满足可以翻转的下标,K-1就是头结点后面个;
如果index+K-1<=7,也就是在下标之内,就可以进行翻转;
index+K-1<=N-1;-->index+K<=N;
这里每次都时从index到index+K-1这样一组来的,当index==0时
就是0-2这k个;index==3,3-5这k个,边界为<index+K;
到这里为止都是用的迭代的方式实现反转,但是不知道为什么,最后两个测试点段错误和答案错误;
然后学习了别人的代码,局部反转,通过这个下标来实现next的功能,然后逆序通过下标对应
交换实现反转,很有创意的想法,在数组中确实时很方便的操作;
边界:
奇数时下标:012;中间不交换,于是是中间的前一个,一共K个,K/2==3/2==1,
01234 K=5,K/2==5/2==2;
这里就是下标小于K/2;
偶数时下标:01,无中间;K=2;K/2==2/2==1;也是小于K/2;
比如:
1 2 3 4 5 6 7
0 1 2 3 4 5 6
这里k=3;要反转的段数是N/K==7/3=2;反转前两段;
第一段;
这里用的是交换实现逆序,用一个变量存放第一次交换位置的数据;
temp=data(0);
data(0)=data(2);
data(2)=temp; //这里就实现了交换逆序;
然后这里前半段与后半段交换实现逆序,
第二段:
第二段起始位置是3;
temp=data(3);
data(3)=data(5);
data(5)=tmep;
起始位置下标就是,这里考虑到用两层循环,一层描述反转的段数;
一层描述段内的反转;反转段数循环变量暂定为i,内层循环为j;
那么反转开始的下标就应该就是 i*K+j;j每次从0开始;
两段:
第一段:i=0,j=0;
下标:0==i*K+j==0*3+0==0
第二段:i=1,j=0;
下标:3==i*K+j==1*3+0==3
这里i*K是外层段开始的位置,j是内部的循环;这是前半段的下标
然后看后半段的下标;这里i=0,j=0时,前段下标为0,后段下标为2
i=1,j=0时,前端下标为K==3,后段下标为5
段下标为K*(i+1)-1,然后j往后移动,那么后段往前,应该是-j;
后端下标:K*(i+1)-1-j;
K*i+K-1-j;
最后反转完成通过下标找到下一个,然后将地址赋值给next域,就实现了结点的链接;
这里升序的下标就是链表的暗链;因为这样翻转后的下一个结点一定是当前结点的后继
结点;注意最后一个结点没有后继,即下标为N-1的结点next域为-1;
补充:这里采用的反转方式没有问题,但是仍然段错误,那么应该是我这里的输入有
问题;继续判断输入的问题;输入应该没有问题,找构建链表这一部分;
暂时屈服了,实在是看不出来哪里出了问题;把字符串指针域改为整型指针域了;
这里改了int型之后就不是段错误了,这里应该是使用string类出了点问题,要去
学习一下string类细节;
然后这里大数超时了,10的5次方两层循环就是10的10次方
超过for的最大承受10的8次方;
*/
#include<iostream>
#include<string>
#include<cstdio>
using namespace std;
struct LNode{
int Address;
int Data;
int Next;
};
typedef struct LNode Node;
int main(){
//输入头结点地址,结点数N,以及翻转结点数K;
int head; //注意这一结点地址是字符串类型;改为int型了;
int N,K;
cin>>head>>N>>K;
//这里改一下输入,采用随机存储的形式,利用下标作为地址;这里N不超过10的5次方;
Node data[100001]; //这里数组和地址是一样的,从0开始编码;
int index=0;
for(int i=0;i<N;i++){
cin>>index; //输入地址;
cin>>data[index].Data>>data[index].Next;
data[index].Address=index;
}
/*
//输入结点信息;
struct LNode data[N];
for(int i=0;i<N;i++){
cin>>data[i].Address>>data[i].Data>>data[i].Next;
}
*/
struct LNode List[N]; //存放链接后链表;
//构建链表;
int next=head;
index=0; //index指示下标;从0开始;
for(int i=0;i<N;i++){
List[i]=data[next]; //
next=List[i].Next;
/*这里的地址数查看应该是N+1个,一共N个结点,当i==N-1时,next指向最后一个结点的地址
而非-1;这里循环就结束了;但是数据是放进了List的;
*/
if(next==-1){
N=i+1; //这一条实在是不合理,没有理解到,求解;这里是根据别人的与我自己差异的找到的缺失;
/*毕竟这里所有数据已经放进List了,不知道这里这里next等于-1后的操作有什么意义*/
break;
}
}
//反转;这个反转是数组随机存取的特色;
struct LNode exchange;
for(int i=0;i<N/K;i++){ //反转的段数;
for(int j=0;j<K/2;j++){ //下标只到每段的前半段,因为交换到中间就交换完成;
exchange=List[i*K+j];
List[i*K+j]=List[K*(i+1)-1-j]; //末尾与首位交换;
List[K*(i+1)-1-j]=exchange;
}
}
//反转后通过下标暗链进行链接;
for(int i=0;i<N;i++){
if(i<N-1){ //有后继结点的结点next域指向下一个;
List[i].Next=List[i+1].Address;
}
else if(i==N-1){
//最后一个结点无后继,next指针域为-1;
List[i].Next=-1;
}
}
//逐个输出;
for(int i=0;i<N;i++){
//前N-1个都是5位整数格式;
if(i<N-1){
printf("%05d %d %05d\n",List[i].Address,List[i].Data,List[i].Next);
}
else if(i==N-1){
printf("%05d %d %d\n",List[i].Address,List[i].Data,List[i].Next);
}
}
return 0;
}
/*
这里第一次错误是读题不仔细,以为只是前K个反转,后面才发现是每k个就反转一次;
然后就是字符串类不够精细,还不够精准,然后就是数组的灵活运用与这个逆序的操作
很巧妙,多读别人的代码,很有帮助;
*/