之前几次PAT,好几次遇见了链表题——将数据以“节点”的形式给出,以“本地址addr”、“值value”、“下一个地址next”的数据形式,让编码者自行逻辑链接操作。由于平时没有刻意关注过,结果实战时按照模拟的方法去做题,要么思维混乱、耗费很多的时间写代码,要么晦涩地模拟出一坨屎。
我个人之前,首先是将“地址”信息作为“字符串”读入;其次,节点间的联系使用map把它映射成数字id;最后,再按照数字id重新遍历,构建以数字id为下标索引的新数组静态链表,就可以按照数据结构里的链表形式进行链表的操作了。
如何将“虽然题目提供的节点是有链表关系的”方便地转换成“平时习惯的在程序上直接处理的链表关系”?我的这种map来map去的方法,特别绕脑子不说,写代码各种bug,而且在删除、修改节点的时超级麻烦……(ㄒoㄒ)
除上述之外,PAT题目尾节点的next是-1,那么在使用map的时又要讨论一下;因为我是按字符串读入的,-1与“-1”的处理也要注意;以及map要反复更新等等……
我一直认为“模拟题”,如果代码写的不够快,是思维不够迅捷、逻辑链不够强才会被绕晕(虽然确实是)。但总觉得自己的方法不对啊,今天抽出时间把这类的题目解法看一下,眼前一亮,看见了一种通用的链表题解决方法——排序法!
简要概述PAT链表题的思路:所有节点给出的地址都是在10e5范围内的正整数,所以我们直接开这么大的结构数组进行索引。将题目提供的 (Address) (Key) (Next) 信息直接存放在node[Address]之中,按照链表顺序遍历链表后,各个节点按链表顺序进行id编号,最后将整个数组基于id排序就可以将乱序的链表节点有序化!而不同的链表题,要做的只是对排序的条件微调罢了!另外要注意的是,为了使得没有数据的数组元素在排序过程中不会导致干扰,要对节点数组进行初始化,例如设置id默认无穷大。
那么我就按照我的做题顺序来写详细代码过程吧,分别是【1097】-【1032】-【1052】-【1133】。
其中PAT的四道题是在这位博主的博文中注意到的: https://www.cnblogs.com/whale90830/p/11474333.html
参考了柳婼的各题的解法,刷了一下PAT的题【1032】Sharing (25)、【1052】Linked List Sorting (25)、【1097】Deduplication on a Linked List (25)、【1133】Splitting A Linked List (25),来检验一下可行度。
1、【1097】Deduplication on a Linked List (25)——节点去重
这题我按照自己的模拟+map的思路写了40分钟,不但bug极多而且删除节点环节没办法跑。学习了柳婼的代码以后学习到了“排序法”。而我觉得,如果这道题目的排序法能够理解,后面的3道题就绝对不是问题了。
题意:给出N个链表节点与起始地址,节点形式按照 (Address) (Key) (Next) 。题目要求将N个节点中,保留首个出现的节点将后续时出现的绝对值相等的节点删去,并单独成另外的链表。节点形式按照 (Address) (Key) (Next) ,在结尾输出这两条链表。
思路:首先将链表节点存入结构体数组中,然后遍历链表,如果这个节点是第一次出现,那么更新标记,以及更新id为节点号 i ;如果这个节点的绝对值已经出现过,那么就需要删除并插入第二张表,于是!!!我们就把这个节点更新为 i+maxn !!!然后按照上文提到的,将整个节点数组node按照id进行排序,就可以得到“按照链表顺序的”且“原表在前,被删除的节点构成的新表在后的”节点顺序了,直接按序输出即可!
这个删除节点的 id赋值为 i+maxn 的优点就在于,由于i变量在每个节点都会+1操作,那么被删除的节点之间,后删除的节点的id必然比前删除的节点要大;而保留的节点与被删除的节点之间,由于被删除的节点的id多了常量 maxn ,所以被删除的节点的id必然要比没有删除的节点要大。在基于各节点的id变量进行sort之后,数组就会如理想的那般有序了!
Code:
#include
using namespace std;
#define inf 100009
#define INF 0x3f3f3f3f
struct Node
{
int id=2*inf;
int addr;
int value;
int next;
bool operator<(const Node& t)const
{
return id>addr>>n; //read Data
for(i=0;i>t;
node[t].addr=t;
cin>>node[t].value>>node[t].next;
}
i=addr; //Traverse the list
int num1=0,num2=0;//num1是没删掉的,num2是删掉的
while(i!=-1)
{
if(book[abs(node[i].value)])
node[i].id=(num2++)+inf;
else
{
node[i].id=num1++;
book[abs(node[i].value)]=1;
}
i=node[i].next;
}
sort(node,node+inf); //sort
for(i=0;i
如果我的代码不够清晰,非常惭愧,可以看看柳婼的写法,更加简明扼要。
2、【1032】Sharing (25)
接下来的三道题都是基本的练手了。
题意:给出N个链表节点与起始地址,节点形式按照 (Address) (Key) (Next) 。题目表示,有两个字符串,都按照链表的形式单字符给出,这两个字符串可能会有相同后缀,即有相同的节点部分。要求在N个节点中,找出重叠部分的首个节点地址,没有输出-1。
思路:首先可以按照上文提到的,基于结构数组的静态链表法,把链表存起来。这题吧我考408的时候就看过了。俩字符串长的那根先走几步,使得剩下的部分和短字符串一样长,接下来俩人手拉手一起走,遇见一个节点是相同的就找到了。如果走到头没找到那就没咯。
坑点: 本题判断是不是相同的节点,看的可不是值是否相同,而是节点的地址是否相同!
Code:
#include
using namespace std;
#define inf 100009
#define INF 0x3f3f3f3f
struct Node
{
int add,next;
}node[inf];
int main()
{
int s1,s2,n,i,j;
cin>>s1>>s2>>n; //read Data
for(i=0;i>t;
node[t].add=t;
cin>>c>>node[t].next;
}
int len1=0,len2=0; //get the length of the two lists
i=s1;
while(i!=-1)
{
len1++;
i=node[i].next;
}
i=s2;
while(i!=-1)
{
len2++;
i=node[i].next;
}
int len=abs(len1-len2);
if(len2>len1)swap(s1,s2);//let the s1 be the longer one
for(i=0,j=s1;i
3、【1052】Linked List Sorting (25)
这题就是妥妥的模板题了!
题意:给出N个链表节点与起始地址,节点形式按照 (Address) (Key) (Next) 。题目要将链表排个序,然后节点形式按照 (Address) (Key) (Next) ,在结尾输出这链表。
思路:这题按照“排序法”妥妥的模板题啊。首先可以按照上文提到的,基于结构数组的静态链表法,把链表存起来。然后按照链表顺序遍历链表,并记录节点的id值,将整个数组基于id排序就可以将乱序的链表节点有序化!基操,非常好理解。
坑点: 1) Emmm…… 没错,有一些节点不在链表上,也接设立一个标记flag或者id+maxn都可以的。
2)可能会0输出,这个时候输出"0 -1\n"才行,不要输出多咯!
Code:
#include
using namespace std;
#define inf 100009
#define INF 0x3f3f3f3f
struct Node
{
int add,next;
int value=INF;
int flag;//是否是链表中的有效节点,默认0
bool operator< (const Node& t)const //比较函数
{
if(flag!=t.flag)return flag>t.flag;//1优先,即在链表的优先排序
return value>n>>s;//read Data
for(i=0;i>t;
node[t].add=t;
cin>>node[t].value>>node[t].next;
}
while(s!=-1)//遍历链表,并标记是否on list
{
node[s].flag=1;
s=node[s].next;
num++;
}
sort(node,node+inf);//sort
if(!num)//如果全是链表外的节点,无输出——这里不注意就会有个点WA
{
printf("0 -1\n");
return 0;
}
else printf("%d %05d\n",num,node[0].add);
for(i=0;i
4、【1133】Splitting A Linked List (25)
题意:给出N个链表节点与起始地址,节点形式按照 (Address) (Key) (Next) 。题目还给了一个正数K,将节点里的数据,如果是负数就要在前面,如果是 [0,K] 之间的数就要在中间,如果大于K就要在最后。然后节点形式按照 (Address) (Key) (Next) ,在结尾输出这链表。
思路:这不和上面一样嘛,大不了就是排序条件不一样呗。首先可以按照上文提到的,基于结构数组的静态链表法,把链表存起来。然后按照链表顺序遍历链表并记录id顺序,如果是负数的节点,标记为rank=1;如果是区间 [0,K] 的节点,标记为rank=2;如果是大于K,标记为rank=3;排序的时候按照标记来排序,如果标记相同按id顺序来。微调一下排序条件而已,问题不大,没有数据的节点就设置rank=4、5、6…都行啦~还是“排序”作为灵魂。
坑点: 没错,有一些节点不在链表上。也设立一个标记flag什么的。【链表题的坑点就是这种
Code:
#include
using namespace std;
#define inf 100009
#define INF 0x3f3f3f3f
struct Node
{
int addr,value,next;
int id,level=3; //默认3,即无数据情况
int flag;//是否在list中
bool operator<(const Node&t)const
{
if(flag!=t.flag)return flag>t.flag;//On list 优先
if(level!=t.level)return level
这里还插入一段小插曲——我还翻出了去年自己用 模拟法+map 过的此题,写的那叫一个销魂苦涩,都是AC的代码,但是我举得还是排序法Nice啊,放到这里对比一下,清晰可见呐简直了!
苦涩的含泪Code:
#include
#include
using namespace std;
#define inf 100009
#define INF 0x3f3f3f3f
#define loop(x,y,z) for(x=y;xadd2pos;
struct Node
{
int add,value,next;
int negtive;
int minK;
}node[inf];
int book[inf];
int isList[inf];
int e[inf];//链表顺序
int main()
{
int i,j;
int s1;
scanf("%d%d%d",&s1,&n,&k);
loop(i,0,n)e[i]=-1;
loop(i,0,n)
{
scanf("%d%d%d",&node[i].add,&node[i].value,&node[i].next);
if(node[i].add==s1)s=i;//定起点
if(node[i].next==-1)g=i;//定终点
if(node[i].value<0)node[i].negtive=1;
if(node[i].value<=k)node[i].minK=1;
add2pos[node[i].add]=i;
}
//串链表,e[i]表示第i个结点的索引
i=s;j=0;
while(1)
{
isList[i]=1;
e[j++]=i;
if(i==g)break;
i=add2pos[node[i].next];
}
queueq;
while(!q.empty())q.pop();
loop(i,0,n)
{
j=e[i];
if(isList[j]==1&&!book[i]&&node[j].negtive)
{
q.push(node[j]);
book[i]=1;
}
}
loop(i,0,n)
{
j=e[i];
if(isList[j]==1&&!book[i]&&node[j].minK)
{//printf("debuggggg : %d %d\n",j);
q.push(node[j]);
book[i]=1;
}
}//printf("debuggggg : %d\n",q.size());
loop(i,0,n)
{
j=e[i];
if(isList[j]==1&&!book[i])
{
q.push(node[j]);
book[i]=1;
}
}//printf("debuggggg : %d\n",q.size());
Node t=q.front();q.pop();
printf("%05d %d ",t.add,t.value);
while(!q.empty())
{
Node &t=q.front();
q.pop();
printf("%05d\n%05d %d ",t.add,t.add,t.value);
}
printf("-1\n");
return 0;
}
好啦,希望本文能起到将PAT链表题如何击破的作用啦~继续努力,动手之后都是小问题!