爆刷PAT(甲级)——之链表专题【1032】【1052】【1097】【1133】——一个排序法应对大部分链表题

之前几次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<bits/stdc++.h>
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<t.id;
    }
}node[inf];
int book[inf];
int main()
{
    int addr,n,i;
    memset(book,0,sizeof book);//init
    cin>>addr>>n; //read Data
    for(i=0;i<n;i++)
    {
        int t;
        cin>>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<num1+num2;i++)    //output
        if(i!=num1-1&&i!=num1+num2-1)printf("%05d %d %05d\n",node[i].addr,node[i].value,node[i+1].addr);
        else printf("%05d %d -1\n",node[i].addr,node[i].value);
    return 0;
}

如果我的代码不够清晰,非常惭愧,可以看看柳婼的写法,更加简明扼要。

 

2、【1032】Sharing (25)

       接下来的三道题都是基本的练手了。

题意:给出N个链表节点与起始地址,节点形式按照 (Address) (Key) (Next) 。题目表示,有两个字符串,都按照链表的形式单字符给出,这两个字符串可能会有相同后缀,即有相同的节点部分。要求在N个节点中,找出重叠部分的首个节点地址,没有输出-1

思路:首先可以按照上文提到的,基于结构数组的静态链表法,把链表存起来。这题吧我考408的时候就看过了。俩字符串长的那根先走几步,使得剩下的部分和短字符串一样长,接下来俩人手拉手一起走,遇见一个节点是相同的就找到了。如果走到头没找到那就没咯。

坑点: 本题判断是不是相同的节点,看的可不是值是否相同,而是节点的地址是否相同

 

Code:

#include<bits/stdc++.h>
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<n;i++)
    {
        int t;
        char c;
        cin>>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<len;i++,j=node[j].next); //move the s1 of the list1
    s1=j;
    while(s1!=-1)//move two lists together
    {
        if(s1==s2)
            break;
        s1=node[s1].next;
        s2=node[s2].next;
    }
    if(s1==-1)printf("-1\n");//output
    else printf("%05d\n",s1);
    return 0;
}

 

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<bits/stdc++.h>
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<t.value;
    }

}node[inf];

int main()
{
    int n,s;
    int i,j,num=0;
    cin>>n>>s;//read Data
    for(i=0;i<n;i++)
    {
        int t;
        cin>>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<num-1;i++)//output
        printf("%05d %d %05d\n",node[i].add,node[i].value,node[i+1].add);
    printf("%05d %d -1\n",node[i].add,node[i].value);

    return 0;
}

 

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<bits/stdc++.h>
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<t.level;//符合题目规则的优先
        return id<t.id;//其次按照原来的顺序
    }
}node[inf];
int main()
{
    int n,k,s,i,j;
    scanf("%d%d%d",&s,&n,&k);//read Data
    for(i=0;i<n;i++)
    {
        int t;
        scanf("%d",&t);
        node[t].addr=t;
        scanf("%d%d",&node[t].value,&node[t].next);
        if(node[t].value<0)node[t].level=0; //负数
        else if(node[t].value<=k)node[t].level=1;// [0,k]之间
        else node[t].level=2; //大于k
    }
    i=s;//遍历链表,判断是否在链表上
    j=1;
    int num=0;
    while(i!=-1)
    {
        node[i].id=j++;
        node[i].flag=1;//The node is on list.
        i=node[i].next;
        num++;//The number of the nodes which are on list.
    }
    sort(node,node+inf);//sort
    for(i=0;i<num-1;i++)//output
        printf("%05d %d %05d\n",node[i].addr,node[i].value,node[i+1].addr);
    printf("%05d %d -1\n",node[i].addr,node[i].value);
    return 0;
}

这里还插入一段小插曲——我还翻出了去年自己用 模拟法+map 过的此题,写的那叫一个销魂苦涩,都是AC的代码,但是我举得还是排序法Nice啊,放到这里对比一下,清晰可见呐简直了!

苦涩的含泪Code:

#include<bits/stdc++.h>
#include<unordered_map>
using namespace std;
#define inf 100009
#define INF 0x3f3f3f3f
#define loop(x,y,z) for(x=y;x<z;x++)

int n,s,g,k;
unordered_map<int,int>add2pos;
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];
    }
    queue<Node>q;
    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链表题如何击破的作用啦~继续努力,动手之后都是小问题!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章