题目描述:

To store English words, one method is to use linked lists and store a word letter by letter. To save some space, we may let the words share the same sublist if they share the same suffix. For example, loading and being are stored as showed in Figure .
Sharing_数据
 

You are supposed to find the starting position of the common suffix (e.g. the position of i in Figure 1).

输入格式:

Each input file contains one test case. For each case, the first line contains two addresses of nodes and a positive N (≤10^5), where the two addresses are the addresses of the first nodes of the two words, and N is the total number of nodes. The address of a node is a 5-digit positive integer, and NULL is represented by −1.

Then N lines follow, each describes a node in the format:

Address Data Next

whereAddress is the position of the node, Data is the letter contained by this node which is an English letter chosen from { a-z, A-Z }, and Next is the position of the next node.

输出格式:

For each case, simply output the 5-digit starting position of the common suffix. If the two words have no common suffix, output -1 instead.

输入样例 1 :

11111 22222 9
67890 i 00002
00010 a 12345
00003 g -1
12345 D 67890
00002 n 00003
22222 B 23456
11111 L 00001
23456 e 67890
00001 o 00010

输出样例 1:

67890

输入样例 2:

00001 00002 4
00001 a 10001
10001 s -1
00002 a 10002
10002 t -1

输出样例 2:

-1

 

题意:

给出两条链表的首地址以及若干结点的地址、数据、下一个结点的地址,求两条链表的首个共用结点的地址。如果两条链表没有共用结点,则输出-1。

思路:

步骤1:由于地址的范围很小,因此可以直接用静态链表,但是依照题目的要求,在结点的结构体中再定义一个int型变量flag,表示结点是否在第一条链表中出现,是则为1,不是为-1。

步骤2:由题目给出的第一条链表的首地址出发遍历第一条链表,将经过的所有结点的fag值赋为1。

接下来枚举第二条链表,当出现第一个flag值为1的结点,说明是第一条链表中出现过的结果,即为两条链表的第一个共用结点。

如果第二条链表枚举完仍然没有发现共用结点,则输出-1。

注意点:

①使用%05d格式输出地址,可以使不足5位的整数的高位补0。

②使用map容易超时。

③ scanf使用%c格式时是可以读入空格的,因此在输入地址、数据、后继结点地址时,格式不能写成%d%c%d,必须在中间加空格。

 

程序代码:

#include<cstdio>
#include<cstring>
const int maxn = 100010;
struct NODE {
char data; //数据域
int next; //指针域
bool flag; //结点是否在第一条链表中出现
}node[maxn];
int main(){
for(int i=0;i<maxn;i++){
node[i].flag = false;
}
int s1,s2,n; //s1,s2分别表示两条链表的首地址
scanf("%d%d%d",&s1,&s2,&n);
int address,next; //结点地址和后继结点地址
char data; //数据
for(int i=0;i<n;i++){
scanf("%d %c %d",&address,&data,&next);
node[address].data = data;
node[address].next = next;
}
int p;
for(p=s1;p!=-1;p=node[p].next) {
node[p].flag = true; //枚举第一条链表的所有结点,令其出现次数为1
}
for(p=s2;p!=-1;p=node[p].next) {
//找到第一个已经在第一条链表中出现的结点
if(node[p].flag == true)
break;
}
if(p!=-1) { //如果第二条链表还没有到达结尾,说明找到了公共结点
printf("%05d\n",p);
}else {
printf("-1\n");
}
return 0;
}

运行结果:

 

Sharing_数据_02

Sharing_链表_03

 

以上是静态链表所能解决的比较简单的题,而对一些稍微复杂的题,此处归纳出了一类问题的通用解题步骤,希望读者能结合后面的例题学习一下。

①定义静态链表。代码如下:

struct Node {
int address; //结点地址
typename data; //数据域
int next; //指针域
xxx; //结点的某个性质 ,不同的题目会有不同的设置,详见例题
}node[maxn];

上面的定义中,我们把结点的地址、数据域、指针域都进行了定义,并且留了一个XXX来适应不同的题目(例如可以设置成结点是否为链表上的一个结点)。

②在程序的开始,对静态链表进行初始化。一般来说,需要对定义中的XXX进行初始化,将其定义为正常情况下达不到的数字(一般来说需要小于所有能达到的数字,理由在第四步说明),例如对结点是否在链表上这个性质来说,我们可以初始化为0(即 false),表示结点不在链表上。

for(int i=0;i<maxn;i++){
node[i].xxx = 0;
}

③题目一般都会给出一条链表的首结点的地址,那么我们就可以依据这个地址来遍历得到整条链表。需要注意的是,这一步同时也是我们对结点的性质XXX进行标记、并且对有效结点的个数进行计数的时候,例如对结点是否在链表上这个性质来说,当我们遍历链表时,就可以把XXX置为1(即true)。

while(p != -1){    //-1代表链表结束
xxx = 1;
count++;
p = node[p]->next;
}

④由于使用静态链表时,是直接采用地址映射(hash)的方式,这就会使得数组下标不连续,而很多时候题目给出的结点并不都是有效结点(即可能存在不在链表上的结点)。为了能够可控地访问有效结点,一般都需要用对数组进行排序以把有效结点移到数组左端,这样就可以用步骤3得到的count来访问它们。

既然需要把有效结点移到前面,那么就可以用之前定义的XXX来帮忙。在步骤2,XXX需要被初始化为比正常结点的XXX取值要小的数值,这个做法就可以在这一步起到作用。由于无效结点的XXX在步骤3中不会被修改,因此一定比有效结点的XXX小。于是在写sort的排序函数cmp时,就可以在cmp的两个参数结点中有无效结点时按XXX从大到小排序,这样就可以把有效结点全部移到数组左端。 

一般来说,题目一定会有额外的要求,因此cmp函数中一般都需要有第二级排序,不过这需要以不同的题目要求来确定。例如,如果题目的要求需要把链表按结点顺序排序,就需要在cmp函数中建立第二级排序,即在cmp的两个参数结点中有无效结点时按XX从大到小排序,而当两个参数结点都是有效结点时按结点在链表中的位置从小到大排序(结点的顺序可以在第三步得到)。

bool cmp(Node a, Node b) {
if(a.xxx == -1 || b.xxx ==-1){
//至少一个结点是无效结点,就把它放在数组后面
return a.xxx>b.xxx;
}else {
//二级排序
}
}

⑤在经历了步骤4后,链表中的有效结点就都在数组左端了,且已经按结点的性质进行了排序,接下来就要看题目在排序之后具体要求做什么了(比较常见的是按各种不同的要求输出链表)。