一、前言
学习完链表相关的知识, 这里通过示例题来对链表进行一下复习 这里使用的链表结构如下:
typedef int LBData;
typedef int Status;
//定义结点
typedef struct Node{
LBData data;
struct Node *next;
}Node;
typedef struct Node * LBList;
注:题目提到的链表均使用单链表, 并且有一个头节点
二、题目
2.1、题目一
题目: 将2个递增的有序链表合并为一个链表的有序链表.
要求: 链表仍然使用两个链表的空间, 不占用额外空间, 不允许重复数据.
分析:
- 假设链表a和b. 因为要不使用额外的空间, 所以新的链表c的指针是指向a或者b的头节点
- 取出a与b中第一个做比较, 小的一方则链接在c的后方,并且指向下一个节点, 大的一方则不动. 若相同, 取其中一方, 另一方释放. 重复这个步骤
- 假设当a为空, 则直接将剩余的b节点排在a的末尾
代码:
void MergeList(LBList *La, LBList *Lb, LBList *Lc) {
LBList la = NULL, lb = NULL, lc, temp;
la = (*La)->next;
lb = (*Lb)->next;
lc = *La;
*Lc = lc;
while (la && lb) {
if ( lb->data == la->data) {
lc->next = la;
lc = la;
la = la->next;
temp = lb->next;
free(lb);
lb = temp;
} else if (lb->data > la->data) {
lc->next = la;
lc = la;
la = la->next;
}else if (lb->data < la->data) {
lc->next = lb;
lc = lb;
lb = lb->next;
}
}
lc->next = la ? la : lb;
free(*Lb);
}
2.2、题目二
题目: 已知两个链表A和B分别表示两个集合. 其元素递增排列. 设计一个算法, 用于求出A与B的交集, 并存储在A链表中;
示例: La = {2, 4, 6, 8}; Lb = {4, 6, 8, 10}; Lc = {4, 6, 8};
分析:
- 定义Lc指向La的头节点
- 遍历Lb. 判断Lb是否大于La. 是则La继续下一个节点
- 是否小于La, 则Lb继续下一个节点.
- 相等则加入到Lc中
- 某一个到末尾则, 则释放另一个剩余的节点
代码实现:
void Intersection(LBList *La, LBList *Lb, LBList *Lc) {
LBList la = NULL, lb = NULL, lc, temp;
la = (*La)->next;
lb = (*Lb)->next;
lc = *La;
*Lc = lc;
while (la && lb) {
if (la->data < lb->data) {
temp = la;
la = la->next;
free(temp);
} if (la->data > lb->data) {
temp = lb;
lb = lb->next;
free(temp);
} else {
temp = lb;
lb = lb->next;
free(temp);
lc->next = la;
lc = la;
la = la->next;
}
}
while (!la) {
temp = lb;
lb = lb->next;
free(temp);
}
while (!lb) {
temp = la;
la = la->next;
free(temp);
}
lc->next = NULL;
free(*Lb);
}
2.3、题目三
题目: 设计一个算法, 将链表中所有节点的链接方向‘原地旋转‘
要求: 仅仅利用原表的存储空间, 空间复杂度为O(1)
例: L = {0, 2, 4, 6, 8, 10} , 逆转后: L = {10, 8, 6, 4, 2, 0}
分析:
- 逆转就相当于把最后元素依次插入 1 2 3 4...的位置, 但是依次读取最后的元素需要经历N次遍历链表, 时间复杂度太大
- 拿到第一个节点, 置空链表的第一个节点.
- 遍历链表, 将每个节点依次用头插法插入链表的第一个节点
代码:
void Inverse(LBList *L){
LBList current = (*L)->next;//正在遍历的当前节点
LBList first = (*L)->next;//链表的第一个节点
(*L)->next = NULL;
while (current != NULL) {
LBList temp = current->next;
current->next = first;
first = current;
current = temp;
}
}
2.3、题目四
题目: 设计一个算法, 删除递增有序链表中值大于等于mink且小于等于maxk的所有元素
例: L = {0, 2, 4, 6, 8, 10} mink = 3; maxk = 6, 删除后: L = {0, 2, 8, 10}
分析:
- 依次遍历列表, 判断下一个节点值是否大大于mink, 记录此时节点A
- 判断下一个节点值是否小于等于maxk, 并且释放经过的节点. 如果为否则记录此时节点的下一个节点B,
- 将A与B链接
代码:
void DeleteMinMax(LBList *L, int mink, int maxk){
LBList lb = (*L)->next;
LBList minkNode = NULL;//, maxkNode = NULL;
while (lb && lb->data < mink) {
minkNode = lb;
lb = lb->next;
}
while (lb && lb->data <= maxk) {
LBList temp = lb;
lb = lb->next;
free(temp);
}
minkNode->next = lb == minkNode ? NULL : lb;
}
2.4、题目五
题目:
设将n(n>1)个整数存放到一维数组R中, 试设计一个在时间和空间两方面都尽可能高效的算法;将R中保存的序列循环左移p个位置(0<p<n)个位置, 即将R中的数据由(x0,x1,......,xn-1)变换为(xp,xp+1,...,xn-1,x0,x1,...,xp-1).
例如:
pre[10] = {0,1,2,3,4,5,6,7,8,9};n = 10,p = 3;
pre[10] = {3,4,5,6,7,8,9,0,1,2};
分析:
- 首先将数组反转成 9 8 7 6 5 4 3 2 1 0
- 将数组分割成 9 8 7 6 5 4 3 | 2 1 0
- 采用两极反转的办法, 分别反转两个数组 变成 3 4 5 6 7 8 9 | 0 1 2
代码:
//首先写一个用于反转数组的方法
//left: 最左边的下标, right: 最右边的下标, 依次交换左右对称的数
void Reverse(int *pre,int left ,int right){
int l = left;
int r = right;
while (l < r) {
int temp = pre[l];
pre[l] = pre[r];
pre[r] = temp;
l++;
r--;
}
}
void LeftShift(int *pre,int n,int p){
if (p < n && p > 0) {
//反转原数组
Reverse(pre, 0, n);
//反转 0 ~ n-p
Reverse(pre, 0, n - p);
//反转 n-p ~ 0
Reverse(pre, n-p, n);
}
}
2.5、题目六
题目:
已知一个整数序列A = (a0,a1,a2,...an-1),其中(0<= ai <=n),(0<= i<=n). 若存在ap1= ap2 = ...= apm = x,且m>n/2(0<=pk<n,1<=k<=m),则称x 为 A的主元素.
设计一个尽可能高效的算法,找出数组元素中的主元素,若存在主元素则输出该元素,否则输出-1.
例如:
A = (0,5,5,3,5,7,5,5),则5是主元素; 若B = (0,5,5,3,5,1,5,7),则A 中没有主元素,假设A中的n个元素保存在一个一维数组中,
分析:
- 采用候选人的办法, 假设第一个元素为候选人, 保存在key中, 记录个数为1
- 遇到下一个元素如果与候选人一致则个数+1否则-1
- 如果个数变成0则把遇到的下一个元素变为候选人, 记录个数为1
- 遍历完成后看记录的个数是否大于0
- 遍历数组, 记录当前候选人是否大于总个数的一半, 大于则候选人变为主元素, 否则不存在主元素
代码:
int MainElement(int *A, int n){
int count = 1;//个数
int key = A[0];//候选人
for (int i = 1; i < n; i++) {
if (A[i] == key) {
count++;
} else {
count--;
if (count == 0) {
key = A[i];
count = 1;
}
}
}
//如果count >0
if (count >0){
//统计候选主元素的实际出现次数
for (int i = count = 0; i < n; i++) {
if (A[i] == key) count++;
}
}
//判断count>n/2, 确认key是不是主元素
if (count > n/2) return key;
return -1; //不存在主元素
}
2.6、题目七
题目:
用单链表保存m个整数, 结点的结构为(data,link),且|data|<=n(n为正整数). 现在要去设计一个时间复杂度尽可能高效的算法. 对于链表中的data 绝对值相等的结点, 仅保留第一次出现的结点,而删除其余绝对值相等的结点.
例如:
链表A = {21,-15,15,-7,15}, 删除后的链表A={21,-15,-7};
分析:
- 采用散列表的方法. 申请一个空间用来记录每个值出是否出现过, 用值作为这个空间的下标
- 遍历节点, 去记录空间里查找这个节点的值是否为1
- 出现过则删除这个节点
代码:
void DeleteEqualNode(LBList *L,int n){
char *p = (char *)malloc(n * sizeof(char));
memcpy(&p, 0, n * sizeof(char));
LBList r = *L;
LBList lb = (*L)->next;
while (lb) {
if (p[abs(lb->data)]) {
r->next = lb->next;
free(lb);
lb = r->next;
} else {
p[abs(lb->data)] = 1;
r = lb;
lb = lb->next;
}
}
}