7-52 两个有序链表序列的交集 (20分)
已知两个非降序链表序列S1与S2,设计函数构造出S1与S2的交集新链表S3。
输入格式:
输入分两行,分别在每行给出由若干个正整数构成的非降序序列,用−1表示序列的结尾(−1不属于这个序列)。数字用空格间隔。
输出格式:
在一行中输出两个输入序列的交集序列,数字间用空格分开,结尾不能有多余空格;若新链表为空,输出NULL
。
输入样例:
1 2 5 -1
2 4 5 8 10 -1
输出样例:
2 5
题意及坑点分析
此题出得不好,其中“交集序列”的定义没有给清楚。到底是两个序列中尽可能多地相同为“交集”,还是按照集合的无重复元素的定义。更坑的是,其测试点没有专门列出这种情况,完全是在考验你黑箱猜模型的能力。
坑点1:“交集序列”是可以有重复元素的。
- “交集序列”的解释如下:
- “交集序列”的元素必须是从输入的两个序列中的一对相同元素组成(二合一);
- 并且每组成一个输出元素,必须各自消耗掉两个序列中的一个元素(用一次消耗一对);
- “交集序列”必须尽可能多,也就是重复1,2操作直到两个序列元素的集合无交集(但可以有重复的元素在同一个序列)!
坑点2:
- 不要忘记考虑无交集和空序列的情况输出"NULL"。
坑点样例:
样例输入:
1 2 3 3 4 4 5 6 -1
1 1 2 2 3 3 4 -1
样例输出:
1 2 3 3 4
(特意将“交集序列”的每一个元素及其对应的来源于输入序列的哪个元素着上相同的颜色,未使用到“交集序列”中的元素为黑色)
数组解法:(有作弊嫌疑)
1.“二合一”的归并排序法(C++)
-
先统一将序列录入,再用归并排序,找到交集元素直接打印。
#include<cstdio>
#include<algorithm>
#include<vector>
#define FOR(i, s, e) for (int i = (s); i<(e); ++i)
using namespace std;
int main() {
vector<int> A[2];
int num;
FOR(i, 0, 2) {
while (scanf("%d", &num) && num!=-1) {
A[i].push_back(num);
}
}
//“二合一”归并排序
bool first = true;
for (size_t i0 = 0,i1=0; i0 < A[0].size() && i1<A[1].size(); ){
if (A[0][i0] < A[1][i1]) {
++i0; //升序序列,小者++才能靠近大者
}else if (A[0][i0] > A[1][i1]) {
++i1;
}else {
if (first) {
printf("%d", A[0][i0]);
first = false;
}
else printf(" %d", A[0][i0]);
++i0; ++i1;
}
}
if (first)puts("NULL"); //一个元素也没有
else puts(""); //换行好看点(虽然可以省略)
system("pause");
return 0;
}
2.数组覆盖解法(C)
- 此解法非常节约空间,故用C语言就可以写。
- 读入第二个序列的同时开始归并,并将归并结果覆盖到第一个序列上,因为“交集序列”每生成一个数就要“用掉”原两个序列各一个数,所以保证不会覆盖到未使用的数。
#include<stdio.h>
#include<stdlib.h>
typedef unsigned int U32;
#define FOR(I,S,E) for(int I=(S);I<(E);I++)
#define MAX(a,b) (((a) > (b)) ? (a) : (b))
#define MSIZE 1000000
U32 A[MSIZE];
U32 size = 0;
int main() {
while (1) {
scanf("%d", &A[size]);
if (-1u == A[size])break; //用-1表示序列的结尾
if (++size >= MSIZE)exit(1); //MSIZE不够用
}
A[size] = -1u; //哨兵!
size = 0; //第二条链表直接覆盖第一条
for (U32 i = 0; 1; ) {
U32 a; scanf("%d", &a);
if (-1u == a)break; //用-1表示序列的结尾
while (a > A[i])++i; //a小于有符号最大值,不会越过A[size]哨兵,故不会越界!
if (a == A[i]) {
A[size++] = a; //第二条链表直接覆盖第一条
++i; //保证用过一次要+1,且size不会超过i。
}
}
if (0 == size)puts("NULL");
else {
printf("%d", A[0]);
for (U32 i = 1; i < size; ++i)
printf(" %d", A[i]);
puts("");
}
system("pause");
return 0;
}
解法二:按题目要求用链表
将此题当作输入的是两个链表表示的有序序列,返回也要是一个链表,表示交集序列。
模仿leetcode,用代码镶嵌的方法改造此题。首先预定义链表节点结构体,还要写好执行的部分,而答题者只需写 Solution类里面的getUnitedList方法。
c++
#include<cstdio>
#define FOR(i, s, e) for (int i = (s); i<(e); ++i)
using namespace std;
struct Node {
int val;
Node *next=NULL;
Node(int val):val(val){}
Node():val(0){}
};
Node *inputList();
void printList(Node *first);
/*--------------------此方法需用户编写--------------------*/
Node *getUnitedList(Node *, Node *);
/*--------------------此方法需用户编写--------------------*/
int main() {
Node *A = inputList(),*B = inputList();
// printList(A); printList(B);
Node *ans = getUnitedList(A, B);
printList(ans);
return 0;
}
Node *inputList() {
Node head, *pre = &head;
int val;
while (scanf("%d", &val) && -1 != val) {
pre = pre->next = new Node(val);
}
return head.next;
}
void printList(Node *first) {
if (first) {
printf("%d", first->val);
while (first = first->next) {
printf(" %d", first->val);
}puts("");
}
else puts("NULL");
}
/*--------------------您的代码将会镶嵌在后面--------------------*/
那么getUnitedList方法可以这么写(由于返回的链表节点抽取自原链表,故会改变原链表)
getUnitedList方法:(放在上面代码最后)
Node *getUnitedList(Node *A, Node *B) {
Node ans, *pre = &ans;
while (A && B) {
Node *temp;
if (A->val < B->val) {
temp = A; A = A->next;
}
else if (A->val > B->val) {
temp = B; B = B->next;
}
else {
temp = A; A = A->next;
pre = pre->next = temp;
temp = B; B = B->next;
}
delete temp;
}
pre->next = NULL;
return ans.next;
}