手撕|应对嵌入式校招面试手撕之——链表

笔者投的是嵌入式软件岗,发现手撕代码难度上比软件开发岗是要简单一些的,基本都是leetcode的medeium和easy的水平,基本难的算法不会考,集中在字符串处理和链表,现在把一些常见的链表题做一下。(以下题目均用c++,在类函数里实现)

1.leetcode 21 合并两个有序链表

输入:l1 = [1,2,4], l2 = [1,3,4] 输出:[1,1,2,3,4,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* mergeTwoLists(ListNode* l1, ListNode* l2) { ListNode *dummy=new ListNode(0); //虚拟头结点不动 ListNode *cur=dummy; //这个结点是变化的 if(l1==nullptr) return l2; if(l2==nullptr) return l1; while(l1!=nullptr&&l2!=nullptr) { if(l1->valval) { cur->next=l1; l1=l1->next; } else { cur->next=l2; l2=l2->next; }cur=cur->next; }if(l1!=nullptr) cur->next=l1; else cur->next=l2; return dummy->next; } };

方法2:递归算法
终止条件:当两个链表都为空时,表示我们对链表已合并完成。
如何递归:判断 l1 和 l2 头结点哪个更小,然后较小结点的 next 指针指向其余结点的合并结果。(调用递归)
class Solution { public: ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {if(l1==nullptr) return l2; if(l2==nullptr) return l1; if(l1->val<=l2->val) { l1->next=mergeTwoLists(l1->next,l2); //较小结点的next指向其余结点 return l1; } else { l2->next=mergeTwoLists(l1,l2->next); return l2; }} };

2、leetcode 23 合并k个升序链表
输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
提示:优先级队列(二叉堆) 这种数据结构,把链表节点放入一个最小堆,就可以每次获得 k 个节点中的最小节点
【手撕|应对嵌入式校招面试手撕之——链表】首先搞一个笨方法,两两合并,借助上边的代码。
/** * 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* mergeTwoLists(ListNode* l1, ListNode* l2) { ListNode *dummy=new ListNode(0); //虚拟头结点不动 ListNode *cur=dummy; //这个结点是变化的 if(l1==nullptr) return l2; if(l2==nullptr) return l1; while(l1!=nullptr&&l2!=nullptr) { if(l1->valval) { cur->next=l1; l1=l1->next; } else { cur->next=l2; l2=l2->next; }cur=cur->next; }if(l1!=nullptr) cur->next=l1; else cur->next=l2; return dummy->next; } ListNode* mergeKLists(vector& lists) { if(lists.size()==0) return nullptr; ListNode* head = lists[0]; for (int i = 1; i < lists.size(); ++i) { head = mergeTwoLists(head, lists[i]); } return head; } };

就是循环一遍,这个方法太蠢了,要搞一个优先级队列来解决这些问题。初始化,我投进去k个有序列表的头结点,优先级判断一遍,取最小的接到dummy上,每投一个把这个结点的下一个结点放入优先级队列,得到新理论的最小接到dummy上,知道当前结点的下一个结点不存在。
/** * 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) {} * }; */ struct cmp{ bool operator()(const ListNode *a,const ListNode *b) { return a->val>b->val; //这里从大到小,队列的头是最大的,队列的尾是最小的,取走一个b,b->next补充 } }; class Solution { public: ListNode* mergeKLists(vector& lists) { if(lists.size()==0) return nullptr; priority_queue,cmp> pq; for(auto list:lists){ if(list) pq.push(list); //初始化,先把头入队,现在自动排序了 }ListNode *dummy=new ListNode(0); ListNode *cur=dummy; while(!pq.empty()) { cur->next=pq.top(); pq.pop(); cur=cur->next; if(cur->next!=nullptr) { pq.push(cur->next); } } return dummy->next; } };

141、环形链表(快慢指针,注意边界条件,fast->next->next已经运行一次了,fast->next就是下一个结点,结合循环理解一下,肯定是快指针先到边界)
/** * Definition for singly-linked list. * struct ListNode { *int val; *ListNode *next; *ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: bool hasCycle(ListNode *head) { if(head==nullptr) //注意:结点为空判断一下 return false; ListNode *fast=head; ListNode *slow=head; while(fast&&fast->next)//注意判断条件,一定是fast先碰到尾部 { slow=slow->next; fast=fast->next->next; if(slow==fast) return true; } return false; } };

142、环形链表2 这个题主要是找到环之后找环的起点,和一个数学公式有关,这里当环相遇的时候,把其中一个结点放在环的起始点,然后每次前进一步,再次相遇,就找到了起点。
/** * Definition for singly-linked list. * struct ListNode { *int val; *ListNode *next; *ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: ListNode *detectCycle(ListNode *head) {ListNode *fast=head; ListNode *slow=head; while(fast&&fast->next)//注意判断条件,一定是fast先碰到尾部 { slow=slow->next; fast=fast->next->next; if(slow==fast) { ListNode *tmp1=slow; while(tmp1!=head) { tmp1=tmp1->next; head=head->next; } return head; } } return nullptr; //注意,没找到返回空} };

876、链表的中间结点
思路:快慢指针,当fasy走道链表末尾时,slow就指向了终点。
/** * 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; if(head==nullptr) return nullptr; while(fast!=nullptr&&fast->next!=nullptr) { slow=slow->next; fast=fast->next->next; //快指针跳出循环时,慢指针到终点} return slow; } };

160、相交链表
给你两个单链表的头节点 headAheadB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null
思路:解决这个问题的关键是,通过某些方式,让 p1p2 能够同时到达相交节点 c1
我们可以让 p1 遍历完链表 A 之后开始遍历链表 B,让 p2 遍历完链表 B 之后开始遍历链表 A,这样相当于「逻辑上」两条链表接在了一起。
如果这样进行拼接,就可以让 p1p2 同时进入公共部分,也就是同时到达相交节点 c1:(这个思路秒啊)
/** * Definition for singly-linked list. * struct ListNode { *int val; *ListNode *next; *ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { ListNode *p1=headA; ListNode *p2=headB; while(p1!=p2) { if(p1==nullptr) p1=headB; //直接让它等于另一个的头结点 else p1=p1->next; if(p2==nullptr) p2=headA; else p2=p2->next; } return p1; } };

19、删除链表的第N个结点。
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
最好一次遍历实现
首先,我们先让一个指针 p1 指向链表的头节点 head,然后走 k 步:
现在的 p1,只要再走 n - k 步,就能走到链表末尾的空指针
用一个指针 p2 指向链表头节点 head,正好走n-k步就可以了。
/** * 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* findFromEnd(ListNode *head,int n) { ListNode *p1=head; ListNode *p2=head; for(int i=0; inext; } while(p1!=nullptr) { p2=p2->next; p1=p1->next; } return p2; } ListNode* removeNthFromEnd(ListNode* head, int n) { ListNode *dummy=new ListNode(0); dummy->next=head; //虚拟节点,防止只有一个节点,删了之后就没有了 ListNode *tmp=findFromEnd(dummy,n+1); //这里是虚拟节点,防止tmp->next->next不存在,找到倒数的n+1个,然后删去 tmp->next=tmp->next->next; return dummy->next; } };

注意细节,当出现p->next->next时,一定想一想p->next不能时nullptr。
接下来会有一个链表专题练习,练个20道。








    推荐阅读