嵌入式大厂面经 链表考点(持续更新中!)
这是一个嵌入式大厂面试题专栏,每天更新高频面试题。专栏将包含题目描述、详细解析、相关知识点扩展以及实际代码示例。内容涵盖操作系统、驱动开发、通信协议等核心领域,并结合实际项目经验进行分析。每道题目都会附带面试官可能的追问方向,帮助大家更好地准备面试!
链表常见面试题总结
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; fas
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
嵌入式面试八股文全集 文章被收录于专栏
这是一个全面的嵌入式面试专栏。主要内容将包括:操作系统(进程管理、内存管理、文件系统等)、嵌入式系统(启动流程、驱动开发、中断管理等)、网络通信(TCP/IP协议栈、Socket编程等)、开发工具(交叉编译、调试工具等)以及实际项目经验分享。专栏将采用理论结合实践的方式,每个知识点都会附带相关的面试真题和答案解析。