爆刷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鏈表題如何擊破的作用啦~繼續努力,動手之後都是小問題!

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