【秋招】嵌入式面试八股文 - 数据结构 链表篇
【秋招】嵌入式面试八股文 - 最全专栏
1. 链表基础概念
Q: 什么是链表?链表有哪些类型?
链表是一种线性数据结构,由一系列节点组成,每个节点包含数据字段和指向下一个节点的引用(指针)。
常见类型:
- 单向链表:每个节点只有一个指向下一节点的指针
- 双向链表:每个节点有两个指针,分别指向前一个和后一个节点
- 循环链表:最后一个节点指向第一个节点,形成环
- 双向循环链表:结合双向链表和循环链表特性
Q: 链表与数组的区别是什么?各自的优缺点?
区别与优缺点:
内存分配 |
动态分配,不连续 |
静态分配,连续空间 |
随机访问 |
O(n) |
O(1) |
插入删除 |
O(1)(已知位置) |
O(n)(需要移动元素) |
空间开销 |
需要额外指针空间 |
无额外开销 |
缓存局部性 |
较差 |
较好 |
大小调整 |
灵活,无需重新分配 |
固定或需要重新分配 |
2. 链表基本操作
Q: 如何实现单链表的创建、插入、删除和遍历?
// 定义链表节点结构
typedef struct Node {
int data;
struct Node* next;
} Node;
// 创建新节点
Node* createNode(int data) {
Node* newNode = (Node*)malloc(sizeof(Node));
if (newNode == NULL) {
printf("内存分配失败\n");
exit(1);
}
newNode->data = data;
newNode->next = NULL;
return newNode;
}
// 在链表头部插入节点
Node* insertAtBeginning(Node* head, int data) {
Node* newNode = createNode(data);
newNode->next = head;
return newNode;
}
// 在链表尾部插入节点
Node* insertAtEnd(Node* head, int data) {
Node* newNode = createNode(data);
// 如果链表为空,新节点就是头节点
if (head == NULL) {
return newNode;
}
// 找到最后一个节点
Node* temp = head;
while (temp->next != NULL) {
temp = temp->next;
}
// 在最后一个节点后添加新节点
temp->next = newNode;
return head;
}
// 删除指定值的节点
Node* deleteNode(Node* head, int key) {
// 如果链表为空,直接返回
if (head == NULL) {
return NULL;
}
// 如果头节点就是要删除的节点
if (head->data == key) {
Node* temp = head;
head = head->next;
free(temp);
return head;
}
// 查找要删除的节点
Node* current = head;
Node* prev = NULL;
while (current != NULL && current->data != key) {
prev = current;
current = current->next;
}
// 如果找到了要删除的节点
if (current != NULL) {
prev->next = current->next;
free(current);
}
return head;
}
// 遍历链表
void traverseList(Node* head) {
Node* current = head;
while (current != NULL) {
printf("%d -> ", current->data);
current = current->next;
}
printf("NULL\n");
}
// 释放链表内存
void freeList(Node* head) {
Node* current = head;
Node* next;
while (current != NULL) {
next = current->next;
free(current);
current = next;
}
}
Q: 如何实现双向链表的基本操作?
// 定义双向链表节点结构
typedef struct DNode {
int data;
struct DNode* prev;
struct DNode* next;
} DNode;
// 创建新节点
DNode* createDNode(int data) {
DNode* newNode = (DNode*)malloc(sizeof(DNode));
if (newNode == NULL) {
printf("内存分配失败\n");
exit(1);
}
newNode->data = data;
newNode->prev = NULL;
newNode->next = NULL;
return newNode;
}
// 在双向链表头部插入节点
DNode* insertAtBeginningDLL(DNode* head, int data) {
DNode* newNode = createDNode(data);
if (head == NULL) {
return newNode;
}
newNode->next = head;
head->prev = newNode;
return newNode;
}
// 在双向链表尾部插入节点
DNode* insertAtEndDLL(DNode* head, int data) {
DNode* newNode = createDNode(data);
if (head == NULL) {
return newNode;
}
DNode* temp = head;
while (temp->next != NULL) {
temp = temp->next;
}
temp->next = newNode;
newNode->prev = temp;
return head;
}
// 删除双向链表中的节点
DNode* deleteNodeDLL(DNode* head, int key) {
if (head == NULL) {
return NULL;
}
// 如果头节点是要删除的节点
if (head->data == key) {
DNode* temp = head;
head = head->next;
if (head != NULL) {
head->prev = NULL;
}
free(temp);
return head;
}
DNode* current = head;
while (current != NULL && current->data != key) {
current = current->next;
}
if (current == NULL) {
return head; // 没找到要删除的节点
}
// 调整前一个节点的next指针
if (current->prev != NULL) {
current->prev->next = current->next;
}
// 调整后一个节点的prev指针
if (current->next != NULL) {
current->next->prev = current->prev;
}
free(current);
return head;
}
// 遍历双向链表
void traverseDLL(DNode* head) {
DNode* current = head;
printf("NULL <-> ");
while (current != NULL) {
printf("%d <-> ", current->data);
current = current->next;
}
printf("NULL\n");
}
3. 链表经典问题
Q: 如何检测链表中是否有环?
Floyd的龟兔算法(快慢指针法):
bool hasCycle(Node* head) {
if (head == NULL || head->next == NULL) {
return false;
}
Node* slow = head;
Node* fast = head;
while (fast != NULL && fast->next != NULL) {
slow = slow->next; // 慢指针每次走一步
fast = fast->next->next; // 快指针每次走两步
if (slow == fast) { // 如果两个指针相遇,说明有环
return true;
}
}
return false; // 如果fast到达链表尾部,说明没有环
}
Q: 如何找到链表的中间节点?
快慢指针法:
Node* findMiddle(Node* head) {
if (head == NULL) {
return NULL;
}
Node* slow = head;
Node* fast = head;
// 快指针每次走两步,慢指针每次走一步
// 当快指针到达链表尾部时,慢指针恰好在中间
while (fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
}
return slow; // 慢指针指向中间节点
}
Q: 如何反转单链表?
迭代方法:
Node* reverseList(Node* head) {
Node* prev = NULL;
Node* current = head;
Node* next = NULL;
while (current != NULL) {
next = current->next; // 保存下一个节点
current->next = prev; // 反转当前节点的指针
prev = current; // 移动prev指针
current = next; // 移动current指针
}
return prev; // 新的头节点
}
递归方法:
Node* reverseListRecursive(Node* head) {
// 基本情况:空链表或只有一个节点
if (head == NULL || head->next == NULL) {
return head;
}
// 递归反转剩余部分
Node* newHead = reverseListRecursive(head->next);
// 改变指针方向
head->next->next = head;
head->next = NULL;
return newHead;
}
Q: 如何合并两个有序链表?
Node* mergeTwoLists(Node* l1, Node* l2) {
// 创建一个哑节点作为合并链表的头部
Node dummy;
Node* tail = &dummy;
while (l1 != NULL && l2 != NULL) {
if (l1->data <= l2->data) {
tail->next = l1;
l1 = l1->next;
} else {
tail->next = l2;
l2 = l2->next;
}
tail = tail->next;
}
// 连接剩余部分
if (l1 != NULL) {
tail->next = l1;
} else {
tail->next = l2;
}
return dummy.next;
}
Q: 如何检测并找到环的入口点?
Node* detectCycleStart(Node* head) {
if (head == NULL || head->next == NULL) {
return NULL;
}
Node* slow = head;
Node* fast = head;
bool hasCycle = false;
// 第一步:检测是否有环
while (fast != NULL && fast->next != NULL) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) {
hasCycle = true;
break;
}
}
if (!hasCycle) {
return NULL;
}
// 第二步:找到环的入口点
slow = head;
while (slow != fast) {
slow = slow->next;
fast = fast->next;
}
return slow; // 环的入口点
}
4. 高级链表问题
Q: 如何判断两个链表是否相交?如何找
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
【秋招】嵌入式八股文最全总结 文章被收录于专栏
双非本,211硕。本硕均为机械工程,自学嵌入式,在校招过程中拿到小米、格力、美的、比亚迪、海信、海康、大华、江波龙等offer。八股文本质是需要大家理解,因此里面的内容一定要详细、深刻!这个专栏是我个人的学习笔记总结,是对很多面试问题进行的知识点分析,专栏保证高质量,让大家可以高效率理解与吸收里面的知识点!掌握这里面的知识,面试绝对无障碍!

查看16道真题和解析