嵌入式软件常用面试题汇总之 C/C++ 语言相关(5)
C/C++之常考链表相关基础编程题汇总
嵌入式的笔试中编程题大部分会是基础的链表操作题,以下挑几道当时做过比较重点经典的,掌握了基本就能通关,参考的解法都是几年前自己做的了,也欢迎各位优化指正!
1.环形链表Ⅱ
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1 输出:返回索引为 1 的链表节点 解释:链表中有一个环,其尾部连接到第二个节点。 示例 2:
输入:head = [1,2], pos = 0 输出:返回索引为 0 的链表节点 解释:链表中有一个环,其尾部连接到第一个节点。 示例 3:
输入:head = [1], pos = -1 输出:返回 null 解释:链表中没有环。 提示:
|
解法参考:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
//简单的方法使用哈希表,但是内存占了n,进阶可以用快慢指针,当指针相遇时,慢指针移到头部,一起继续走,注意快指针此时也改成一步,当再次相遇时就是环的入口。
class Solution {
public:
ListNode *detectCycle(ListNode *head)
{
if(head == NULL || head->next == NULL )
{
return NULL;
}
ListNode* fast= head;
ListNode* slow= head;
while(fast->next != NULL && fast->next->next != NULL)
{
fast = fast->next->next;
slow = slow->next;
if(fast == slow) //找到了开始做处理
{
slow = head;
while(slow != fast)
{
fast = fast->next;
slow = slow->next;
//result++;
}
return slow;
}
}
return NULL;
}
};
2.链表的中间结点
给你单链表的头结点 head ,请你找出并返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
示例 1:
输入:head = [1,2,3,4,5] 输出:[3,4,5] 解释:链表只有一个中间结点,值为 3 。 示例 2:
输入:head = [1,2,3,4,5,6] 输出:[4,5,6] 解释:该链表有两个中间结点,值分别为 3 和 4 ,返回第二个结点。 提示:
|
解法参考:
/*第一印象就是双指针,快慢指针,当快的到末尾(或)时,慢的就是中间结点*/
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* middleNode(ListNode* head)
{
ListNode* fast = head;
ListNode* slow = head;
while(fast != NULL && fast->next != NULL) //短路求值注意判断条件的顺序
{
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
};
3.复杂链表的复制
请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。
示例 1:
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]] 输出:[[7,null],[13,0],[11,4],[10,2],[1,0]] 示例 2:
输入:head = [[1,1],[2,1]] 输出:[[1,1],[2,1]] 示例 3:
输入:head = [[3,null],[3,0],[3,null]] 输出:[[3,null],[3,0],[3,null]] 示例 4: 输入:head = [] 输出:[] 解释:给定的链表为空(空指针),因此返回 null。 提示:
|
解法参考:
/*
// Definition for a Node.
class Node {
public:
int val;
Node* next;
Node* random;
Node(int _val) {
val = _val;
next = NULL;
random = NULL;
}
};
*/
/*
这道题难点就在于多了个随机指针,如果没有随机指针,那么直接搞一个前驱结点pre然后开始遍历就可以了,但多了个随机结点,没办法找到一个像前驱节点这样跟本节点这样挂钩明确的可遍历的,所以没办法直接遍历一遍。
几种方法:
一:借助哈希表,表内容为<Node*,Node*>型,里面为新结点跟旧结点的对应,然后直接遍历一遍把旧结点复制一遍(单纯是赋了val值),然后再通过遍历哈希表,把新结点的next和random给补充完整
二:复制链表,新结点直接连在对应旧节点的后面,1-1*-2-2*-3-3*....这样新结点的next值已经确定好了,接下来就去遍历旧结点(旧结点的遍历是要.next.next),然后确定新节点的random值,因为有新节点.random = 旧节点.random.next,所以遍历完成后就整个复制好了,接下来就是把链表拆分开就可以,拆分也很简单,让旧结点.next = 旧结点.next.next 新节点也同样,遍历就完事,最后返回那个头。用这种方法就空间复杂度只有1
*/
class Solution {
public:
Node* copyRandomList(Node* head) {
if(head == nullptr) return nullptr;
Node* cur = head;
unordered_map<Node*, Node*> map;
// 3. 复制各节点,并建立 “原节点 -> 新节点” 的 Map 映射
while(cur != nullptr) {
map[cur] = new Node(cur->val);
cur = cur->next;
}
cur = head;
// 4. 构建新链表的 next 和 random 指向
while(cur != nullptr) {
map[cur]->next = map[cur->next];
map[cur]->random = map[cur->random];
cur = cur->next;
}
// 5. 返回新链表的头节点
return map[head];
}
};
//这里用下第二种方法,效率高点
/*class Solution {
public:
Node* copyRandomList(Node* head)
{
if(head == NULL) return head;
Node* pos = head;
//Node* posnext = head->next;
while(pos != NULL) //遍历一次链表就把val和next搞定
{
Node* npos = new Node(pos->val);
npos->next = pos->next;
pos->next = npos;
pos = npos->next;
//if(posnext != NULL)
//posnext = posnext->next;
}
pos = head;
while(pos != NULL) //遍历第二次链表就把random搞定
{
//注意在random这里要判断一下这个random是不是指向null,不然null的next没有意义
if(pos->random == NULL) continue;
pos->next->random = pos->random->next;
pos = pos->next->next;
}
pos = head->next;
//posnext = head->next;
Node* result = head->next;
Node* posnext = head;
while(pos->next != NULL) //最后把链表拆开,返回出去
{
pos->next = pos->next->next;
pos = pos->next;
posnext->next = posnext->next->next;
posnext = posnext->next;
}
pos->next = NULL;
return result;
}
};
*/
4.删除排序链表中的重复元素
给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。
示例 1:
输入:head = [1,1,2] 输出:[1,2] 示例 2:
输入:head = [1,1,2,3,3] 输出:[1,2,3] 提示:
|
解法参考:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
//因为链表已经排好序,所以思路就是遍历,如果重复直接跨过去
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head)
{
if(head == NULL || head->next == NULL)
{
return head;
}
ListNode *pos = head;
while(pos->next != NULL)
{
if(pos->val != (pos->next->val))
{
pos = pos->next;
}else
{
pos->next = pos->next->next;
// pos = pos->next; 这一步不能写,写了导致bug 因为这样子若有多个同样重复点,直接跳到那里错过了
}
}
return head;
}
};
5.两两交换链表中的节点
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
示例 1:
输入:head = [1,2,3,4] 输出:[2,1,4,3] 示例 2: 输入:head = [] 输出:[] 示例 3: 输入:head = [1] 输出:[1] 提示:
|
解法参考:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
//看到一个大神教的递归,来尝试写一下
class Solution {
public:
ListNode* swapPairs(ListNode* head)
{
if(head == NULL || head->next == NULL) return head;
ListNode* next = head->next;
head->next = swapPairs(next->next);
next->next = head;
return next;
}
};
6.相交链表
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
图示两个链表在节点 c1 开始相交:
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
自定义评测:
评测系统 的输入如下(你设计的程序 不适用 此输入):
intersectVal - 相交的起始节点的值。如果不存在相交节点,这一值为 0
listA - 第一个链表
listB - 第二个链表
skipA - 在 listA 中(从头节点开始)跳到交叉节点的节点数
skipB - 在 listB 中(从头节点开始)跳到交叉节点的节点数
评测系统将根据这些输入创建链式数据结构,并将两个头节点 headA 和 headB 传递给你的程序。如果程序能够正确返回相交节点,那么你的解决方案将被 视作正确答案 。
示例 1:
输出:Intersected at '8' 解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。 从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。 在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。 — 请注意相交节点的值不为 1,因为在链表 A 和链表 B 之中值为 1 的节点 (A 中第二个节点和 B 中第三个节点) 是不同的节点。换句话说,它们在内存中指向两个不同的位置,而链表 A 和链表 B 中值为 8 的节点 (A 中第三个节点,B 中第四个节点) 在内存中指向相同的位置。 示例 2: 输入:intersectVal = 2, listA = [1,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1 输出:Intersected at '2' 解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。 从各自的表头开始算起,链表 A 为 [1,9,1,2,4],链表 B 为 [3,2,4]。 在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。 示例 3: 输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2 输出:null 解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。 由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。 这两个链表不相交,因此返回 null 。 提示: listA 中节点数目为 m listB 中节点数目为 n 1 <= m, n <= 3 * 104 1 <= Node.val <= 105 0 <= skipA <= m 0 <= skipB <= n 如果 listA 和 listB 没有交点,intersectVal 为 0 如果 listA 和 listB 有交点,intersectVal == listA[skipA] == listB[skipB] |
解法参考:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
/* 想到的几种方法
1:穷举
2:hash表记录第一个遍历的链表
3:双指针,分别从两边的头开始,当一边到null后就换成从另一边的头开始,以此类推,当两者相等时就跳出来返回
4:双指针,不过先分别遍历两个链表的长度,用较长的减去较短的,得到一个差值,然后长的那个指针从头部往后差值个结点开始,短的那个指针从头开始,双双向后移动,直到相遇则为第一个交点,若为null则为不相交
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB)
{
ListNode* a = headA;
ListNode* b = headB;
while(a != b)
{
a = a==NULL?headB:a->next;
b = b==NULL?headA:b->next;
}
return a;
}
};
7.回文链表
给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。
示例 1:
输入:head = [1,2,2,1] 输出:true 示例 2:
输入:head = [1,2] 输出:false 提示:
|
解法参考:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
/*
1、方法一是双指针去遍历,一个从头开始 一个从尾巴,逐个比较直到相遇,过程若有不同直接跳出,注意偶数和奇数的区别,所以还要判断左边的是否跑到右边了 (即直接遍历然后把数都放到数组里面,然后在数组里面进行双指针对比) 但空间复杂度为n
2、方法二是快慢指针结合链表反转,当快的指针到了null(偶数)或下一个就是null时(奇数)即停止,此时慢指针即为中间,(注意若快指针是null则慢指针这里就是中间,若快指针下一个才是null则慢指针要再往下走一步),然后将慢指针为头的链表反转,快指针移动到头部,然后开始遍历比较,直到慢指针到达null 这种做法空间复杂度只为1
*/
class Solution {
public:
bool isPalindrome(ListNode* head)
{
if(head->next == NULL) return true;
ListNode* fast = head;
ListNode* slow = head;
while(fast != NULL && fast->next != NULL ) //将快指针指到末尾 或是null或是下一个是null
{
fast = fast->next->next;
slow = slow->next;
}
if(fast != NULL) slow = slow->next; //则此时链表是奇数链表
slow = listReverse(slow);
fast = head;
while(slow != NULL)
{
if(slow->val != fast->val )
{
return false;
}
slow = slow->next;
fast = fast->next;
}
return true;
}
//之前返回void 直接修改 一直有bug 后面改了,直接返回反转后的头结点吧
ListNode* listReverse(ListNode* head) //链表反转的实现函数
{
if(head->next == NULL) return head;
ListNode* result = NULL;
ListNode* pos = head;
ListNode* last = head->next;
while(pos != NULL )
{
pos->next = result;
result = pos;
pos = last;
if(last != NULL)
last = last->next;
}
return result;
}
};
#23届找工作求助阵地##链表题##嵌入式##笔试题目##笔面试#该专栏是我整理的一些嵌入式软件笔面试常见的题目,在有一定计算机基础上,再过一遍该专栏的内容,对应届生校招来说基本上笔面试就没什么问题了! 有任何疑问可随时与我联系,一起交流一起进步。













汤臣倍健公司氛围 364人发布