
算法系列篇章-可以参照如下顺序阅读
1. 将2个递增的有序链表合并为一个链表的有序链表; 要求结果链表仍然使⽤两个链表的存储空间,不另外占用其他的存储空间;表中不允许有重复的数据
思路分析:
- 1.1 待合并链表La,Lb,Lc为结果链表
- 1.2 pa,pb分别指向La,Lb的首元节点,Lc指向头结点(因为不允许新建内存空间,所以Lc借用La或者Lb的头节点作为结果连表头节点),pc指向Lc
- 1.3 循环遍历两个链表(La 不为空 && Lb不为空)
- 1.4 判断 La和Lb的data大小,当两者不想等的时候,pc拼接小值,并且pc指向最新的小值,pa或者pb小的那个进行指针后移
- 1.5 处理两者相等的情况,因为Lc指向了La,所以先取pa的小值,并且pa进行指针后移,pb进行后移的同时,释放pb小值所在节点的空间
- 1.6 处理其中一个链表遍历结束的情况,直接将非空链表的部分拼接在pc之后即可
- 1.7 最后我们需要释放Lb
代码实现:
void funcMergerLinkList(LinkList *La,LinkList *Lb,LinkList *Lc){
// 初始化变量
LinkList pa,pb,pc,temp;
pa = (*La)->next;
pb = (*Lb)->next;
pc = *Lc = *La;
while (pa && pb){
if(pa->data < pb->data){ // La的数据小于Lb的数据
// 取小数拼接在pc之后,pa指针后移,pc指向小数
pc->next = pa;
pc = pa;
pa = pa->next;
}else if(pa->data > pb->data){ // a > b的情况
// 取小数拼接在pc之后,pb指针后移,pc指向小数
pc->next = pb;
pc = pb;
pb = pb->next;
}else{ // 相等的情况
// 取小数拼接在pc之后,pa指针后移,pc指向小数
pc->next = pa;
pc = pa;
pa = pa->next;
// 同时pb指针后移,并且释放pb小数空间
temp = pb->next;
free(pb);
pb = temp;
}
}
// 处理超界情况
pc->next = pa?pa:pb;
// 释放*Lb
free(*Lb);
}
结果预期如下:

2. 已知两个链表A和B表示两个集合,其元素递增排列,设计算法求出A与B的交集,并存储在A链表中
思路分析:
- 2.1 初始化临时变量用来指向相关位置
- 2.2 pa,pb指向La,Lb的next,pc和Lc指向La头节点,作为结果链表(仍然不创建新的内存空间)
- 2.3 循环遍历两个(La,Lb不为空)
- 2.4 相等处理,链接相等元素到pc,并且pa指针后移,pb指针后移的同时,释放节点空间
- 2.5 判断两个节点元素大小不相等的情况,小值链表指针后移,并且释放当前节点
- 2.6 处理链表超界情况
- 2.7 尾节点处理以及pb的释放
代码实现:
// 面试题二
void funcConnectInsectionLinkList(LinkList *La, LinkList *Lb, LinkList *Lc){
// 初始化临时变量
LinkList pa,pb,pc,temp;
pa = (*La)->next; // La的首结点
pb = (*Lb)->next; // Lb的首结点
pc = *Lc = *La; // Lc取La的首元节点
while (pa && pb) { // 遍历两个链表,直到某个链表到结尾
if (pa->data == pb->data) { // 找到交集元素
// 取交集元素拼接在pc之后,pa指针后移,pc指向交集元素
pc->next = pa;
pc = pa;
pa = pa->next;
// pb指针后移,释放pb的交集元素空间
temp = pb;
pb = pb->next;
free(temp);
}else if (pa->data > pb->data){ // 主要是为了判断应该移除哪一个的当前节点
temp = pb;
pb = pb->next;
free(temp);
}else{
temp = pa;
pa = pa->next;
free(temp);
}
}
while (pa) { // 边界处理
temp = pa;
pa = pa->next;
free(temp);
}
while (pb) { // 边界处理
temp = pb;
pb = pb->next;
free(temp);
}
pc->next = NULL;
free(*Lb);
}
结果预期:

3. 设计一个算法,将链表中所有节点的链接方向原地旋转,即要求仅仅利用原表的存储空间,换句话说,要求算法空间复杂度为O(1)
思路分析:
- 3.1 p 要指向*L的首元结点
- 3.2 *L 作为旋转后的头结点
- 3.3 从前往后遍历链表,将节点前插到*L
- 3.4 q临时变量指向待插入结点的后继结点
代码实现:
// 面试题三
void funcReverseLinkList(LinkList *L){
// 临时变量
LinkList temp;
LinkList p = (*L)->next;
// *L作为新的头结点
(*L)->next = NULL;
// 循环遍历前插法
while (p) {
// 记录当前p的next
temp = p->next;
// 把p的后继指向*L的后继
p->next = (*L)->next;
// *L的首结点指向p
(*L)->next = p;
// p 向后位移一位
p = temp;
}
}
结果预期:

4. 设计一个算法,删除递增有序链表中大于等于mink且小于maxk(外界给定参数,其值可以和表中的元素相同,也可以不同)的所有元素
思路分析:
- 遍历链表,第一个小于mink的节点-pre
- 遍历链表,第一个大于maxk的节点-next
- pre指向next即可
- 释放删除节点
代码实现:
// 面试题四
void funcDeleteLinkListWithRange(int min, int max, LinkList *L){
// 定义临时变量
LinkList p,q,pre,temp;
p = (*L)->next;
pre = (*L);
// 遍历找到区间小值的节点
while (p && p->data < min) {
pre = p;
p = p->next;
}
// 遍历找到区间大值的节点
while (p && p->data <= max) {
p = p->next;
}
q = pre->next; // 用来保存需要删除的节点位置起点
pre->next = p; // 用来保存需要删除的节点位置终点
while(q != p){
temp = q;
q = q->next;
free(temp);
}
}
结果预期:

5. 设将n(n>1)个整数存放到一维数组R中,设计一个在时间和空间两方面都尽可能高效的算法;将R中保存的序列循环左移p个位置(0<p<n)个位置,即将R中的数据由(x0,x1,......,xn-1)变为(xp,xp+1,...xn-1,x0,x1,.....xp-1)
思路分析:
- 5.1 n个数据原地逆置 xn-1,xn-2,.....x0
- 5.2 拆解成两个部分(xn-1,xn-2,...xp)(xp-1,...x1.x0)
- 5.3 再将这两个部分逆向处理
代码实现:
// 面试题五
void funcReverseArray(int *p, int start, int end){ // 默认 end > start 且都在数组大小内
int i = start;
int j = end;
int temp;
while (i < j) {
temp = p[j];
p[j] = p[i];
p[i] = temp;
i++;
j--;
}
}
void funcLeftShiftArray(int *p, int n, int left){
if (0 < p < n-1) {
funcReverseArray(p, 0, n-1); // 逆序整个数组
funcReverseArray(p, 0, n-1-left); // 逆序前面 n-1-left个元素
funcReverseArray(p, n-left, n-1); // 逆序最后left个元素
}
}
结果预期:

6. 已知一个整数序列列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的主元素. 例,A = (0,5,5,3,5,7,5,5),则5是主元素; 若B = (0,5,5,3,5,1,5,7),则A 中没有主元素,假设A中的n个元素保存在一个一维数组中,请设计一个尽可能⾼效的算法,找出数组元素中的主元素,若存在主元素则输出该元素,否则输出-1.
思路分析:
- 6.1 选择候选主元素,循环计数方式找到链表那个元素出现次数最多
- 6.2 循环计数当前候选人出现的次数
- 6.3 判断当前候选是不是主元素 即次数大于 n/2
代码实现:
// 面试题六
int funcFindMainElement(int *a, int n){
int count = 1; // 记录候选元素出现的标示
int key = a[0]; // 把当前第一个元素当作候选主元素
for (int i = 1; i < n; i++) { // 遍历记录候选主元素出现的标示,找到标示位最多的候补主元素
if (key == a[i]) { // 代表主元素出现+1
count++;
}else{ // 代表当前元素和候选主元素不匹配
if (count > 0) { // 如果标示位大于0
count--;
}else{ // 代表标示位已经没有了,这个时候需要更换候补主元素
key = a[i];
count = 1;
}
}
}
if (count > 0) { // 代表最终候补主元素存在
count = 0;
for (int i = 0; i < n; i++) { // 遍历累加当前候补主元素出现次数
if (key == a[i]) {
count++;
}
}
}
if (count > n / 2) { // 是否满足主元素条件
return key;
}
return -1;
}
结果预期:

7. ⽤单链表保存m个整数, 结点的结构为(data,link),且|data|<=n(n为正整数). 现在要去设计⼀个时间复杂度尽可能⾼效的算法. 对于链表中的data绝对值相等的结点, 仅保留第一次出现的结点,⽽删除其余绝对值相等的结点.例如,链表A = {21,-15,15,-7,15}, 删除后的链表A={21,-15,-7};
思路分析:
- 7.1 因为已知条件 |data|<=n,所以我们可以空间换时间,申请一个长度为n的数组空间,用来标示链表元素出现的标示
- 7.2 对于数组空间所有元素(数组从1开始)初值为0,代表当前没有出现过链表数据
- 7.3 遍历链表,判断当前链表数据data的绝对值作为下标,在数组中的值情况,如果是1代表已经出现过,则删除当前节点,如果值是0,代表当前节点未出现过,将当前节点数据为下标的对应数组的值置为1,代表当前节点已经出现过
代码实现:
// 面试题七
void funcDeleteEuqalNode(LinkList *L, int n){
// 创建辅助数组
int *p = alloca(sizeof(int) * (n + 1));
for (int i = 0; i < n + 1; i++) {
*(p + i) = 0;
}
LinkList q = (*L)->next;
LinkList temp = *L;
while (q) { // 遍历链表
if (p[abs(q->data)] == 1) { // 代表当前节点出现过
// 删除当前节点
temp->next = q->next;
free(q);
q = temp->next;
}else{ // 当前节点没有出现过
p[abs(q->data)] = 1;
temp = q;
q = q->next;
}
}
}
结果预期:
