简单不先于复杂,而是在复杂之后。
链表面试题
- 删除链表中等于给定值 val 的所有结点。OJ链接
//1.常规方法struct ListNode* removeElements(struct ListNode* head, int val) {struct ListNode* cur = head, *prev = NULL;while(cur){if(cur->val == val){//1.头删//2.非头删if(cur == head){head = head->next;free(cur);cur = head;}else{prev-> next = cur->next;free(cur);cur = prev-> next;}}else{prev = cur;cur = cur->next;}} return head; }
//2.新思路 //创建一个新链表,把非val的结点尾插到新链表 struct ListNode* removeElements(struct ListNode* head, int val) {struct ListNode* newhead =NULL, *cur = head, *tail = NULL;while(cur){if(cur->val != val){if(tail == NULL){newhead = tail = cur;}else{tail->next = cur;tail = tail->next;}cur = cur->next;}else{struct ListNode* del = cur;cur = cur->next;free(del);}}//最后的节点是val就会出现此问题if(tail){tail->next = NULL;}return newhead; }
小技巧:在涉及到链表的题的时候调试不太方便,我们需要快速构建一个链表。
int main() {struct ListNode* n1 = (struct ListNode*)malloc(sizeof(struct ListNode));assert(n1);struct ListNode* n2 = (struct ListNode*)malloc(sizeof(struct ListNode));assert(n2);struct ListNode* n3 = (struct ListNode*)malloc(sizeof(struct ListNode));assert(n3);struct ListNode* n4 = (struct ListNode*)malloc(sizeof(struct ListNode));assert(n4);struct ListNode* n5 = (struct ListNode*)malloc(sizeof(struct ListNode));assert(n5);struct ListNode* n6 = (struct ListNode*)malloc(sizeof(struct ListNode));assert(n6);struct ListNode* n7 = (struct ListNode*)malloc(sizeof(struct ListNode));assert(n7);n1->next = n2;n2->next = n3;n3->next = n4;n4->next = n5;n5->next = n6;n6->next = n7;n7->next = NULL;n1->val = 1;n2->val = 2;n3->val = 6;n4->val = 3;n5->val = 4;n6->val = 5;n7->val = 6;return 0; }
带哨兵位的链表:
- 哨兵节点(Sentinel Node): 这是一个特殊的节点,它不包含实际数据,仅用于简化代码逻辑。哨兵节点通常作为链表的头部存在,这意味着链表中始终有一个节点,即使链表为空。哨兵节点的存在简化了对链表头部的特殊处理,因为无论链表是否为空,我们都可以始终通过哨兵节点来引导链表的访问。
- 优点: 哨兵节点可以简化代码实现,避免在对头部进行操作时需要额外的条件检查。
不带哨兵位的链表:
- 特殊处理头部: 在不使用哨兵节点的情况下,需要特殊处理链表头部,因为在空链表或者插入第一个节点时,需要进行额外的条件检查,以确保正确处理链表头部。
- 优点: 节省了一个节点的空间开销,因为没有用于哨兵的额外节点。
带哨兵位和不带哨兵位的区别:
//利用带哨兵位的头节点的链表可以免去判空的步骤struct ListNode* removeElements(struct ListNode* head, int val) {struct ListNode* cur = head;struct ListNode* guard = (struct ListNode*)malloc(sizeof(struct ListNode));struct ListNode* tail = guard;while(cur){if(cur->val != val){tail->next = cur;tail = tail->next;cur = cur->next;}else{struct ListNode* del = cur;cur = cur->next;free(del);}}//最后的节点是val就会出现此问题if(tail){tail->next = NULL;}head = guard->next;free(guard);return head; }
替代之前实现单链表时候传参用的二级指针有两种方式:
- 返回新的链表头指针
- 设计为带哨兵位的链表
(单链表在实际应用中很少带头,OJ题链表基本不带头)
- 反转一个单链表。OJ链接
struct ListNode* reverseList(struct ListNode* head) {struct ListNode* cur = head;struct ListNode* newhead = NULL;while(cur){struct ListNode* next = cur->next;cur->next = newhead;newhead = cur;cur = next;}return newhead; }
//思路2:将链表的指针指向反转 struct ListNode* reverseList(struct ListNode* head) {struct ListNode* prev = NULL;struct ListNode* cur = head;while(cur){struct ListNode* next = cur->next;cur->next = prev;//迭代器prev = cur;cur = next;}return prev; }
- 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。OJ链接
struct ListNode* middleNode(struct ListNode* head) {struct ListNode* fast,*slow;fast = slow = head;while(fast && fast->next){slow = slow->next;fast = fast->next->next;}return slow; }
- 输入一个链表,输出该链表中倒数第k个结点。OJ链接
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {// write code herestruct ListNode* slow, *fast;slow = fast = pListHead;while(k--){//判断k大于链表长度if(!fast){return NULL;}fast = fast->next;}while(fast){slow = slow->next;fast = fast->next;}return slow; }
- 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有结点组成的。OJ链接
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {struct ListNode* guard = (struct ListNode*)malloc(sizeof(struct ListNode));guard->next = NULL;struct ListNode* tail = guard;struct ListNode* cur1 = list1, *cur2 = list2;while(cur1 && cur2){if(cur1->val < cur2->val){tail->next = cur1;cur1 = cur1->next;}else{tail->next = cur2;cur2 = cur2->next;}tail = tail->next;}if(cur1)tail->next = cur1;if(cur2)tail->next = cur2;struct ListNode* head = guard->next;free(guard);return head; }
- 编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大小或等于x的结点之前。OJ链接
class Partition {public:ListNode* partition(ListNode* pHead, int x) {// write code herestruct ListNode* lessTail,*lessGuard,*greaterTail,*greaterGuard;lessTail = lessGuard = (struct ListNode*)malloc(sizeof(struct ListNode));greaterTail = greaterGuard = (struct ListNode*)malloc(sizeof(struct ListNode));struct ListNode* cur = pHead;greaterGuard->next = NULL;lessGuard->next = NULL;while(cur){if(cur->val < x){lessTail->next = cur;lessTail = lessTail->next;}else {greaterTail->next = cur;greaterTail = greaterTail->next;}cur = cur->next;}lessTail->next = greaterGuard->next;greaterTail->next = NULL;pHead = lessGuard->next;free(greaterGuard);free(lessGuard);return pHead;} };
- 链表的回文结构。OJ链表
class PalindromeList {public:struct ListNode* middleNode(struct ListNode* head) {struct ListNode* fast, *slow;fast = slow = head;while (fast && fast->next) {slow = slow->next;fast = fast->next->next;}return slow;}struct ListNode* reverseList(struct ListNode* head) {struct ListNode* prev = NULL;struct ListNode* cur = head;while (cur) {struct ListNode* next = cur->next;cur->next = prev;//迭代器prev = cur;cur = next;}return prev;}bool chkPalindrome(ListNode* head) {// write code herestruct ListNode* mid = middleNode(head);struct ListNode* rmid = reverseList(mid);while(head && rmid){if(head->val != rmid->val)return false;head = head->next;rmid = rmid->next;}return true;} };
- 输入两个链表,找出它们的第一个公共结点。OJ链表
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {struct ListNode* curA = headA, *curB = headB;if(headA == NULL || headB == NULL){return NULL;}int lenA = 1;while(curA->next){curA = curA->next;lenA++;}int lenB = 1;while(curB->next){curB = curB->next;lenB++;}if(curA != curB){return NULL;}struct ListNode* longList = headA,*shortList = headB;if(lenA < lenB){longList = headB;shortList = headA;}int gap = abs(lenA - lenB);while(gap--){longList = longList->next;}while(longList != shortList){longList = longList->next;shortList = shortList->next;}return longList; }
- 给定一个链表,判断链表中是否有环。OJ链表
bool hasCycle(struct ListNode *head) {struct ListNode* fast, *slow;fast = slow = head;while(fast && fast->next){slow = slow->next;fast = fast->next->next;if(slow == fast)return true;}return false; }
【思路】
快慢指针,即慢指针一次走一步,快指针一次走两步,两个指针从链表起始位置开始运行,如果链表带环则一定会在环中相遇,否则快指针率先走到链表的末尾。
【扩展问题】
- 为什么快指针每次走两步,慢指针走一步可以?
假设链表带环,两个指针最后都会进入环,快指针先进环,慢指针后进环。当慢指针刚进环时,可能就和快指针相遇了,最差情况下两个指针之间的距离刚好就是环的长度。
此时,两个指针每移动一次,之间的距离就缩小一步,不会出现每次刚好是套圈的情况,因此:在慢指针走到一圈之前,快指针肯定是可以追上慢指针的,即相遇。
快指针一次走3步,走4步,…n步行吗?
快指针一次走 3 步:
假设slow进环后,fast和slow之间差距N,追赶距离就是N,每追赶一次,之间的距离缩小2步
如果N是偶数,距离一定会减少到0,也就是两个指针相遇;
如果N是奇数,距离会减少到1,fast会越过slow指针,fast和slow之间得距离变成了-1,也就是C-1,(C-1是环长度),如果C-1是偶数,再追一圈就可以追上,如果C-1是奇数,永远追不上;
- 给定一个链表,返回链表开始入环的第一个结点。如果链表无环,则返回 NULL。OJ链接
struct ListNode *detectCycle(struct ListNode *head) {struct ListNode* slow = head, *fast = head;while(fast && fast->next){slow = slow->next;fast = fast->next->next;if(slow == fast){struct ListNode* meet = slow;while(meet != head){meet = meet->next;head = head->next;}return meet;}}return NULL; }
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {struct ListNode* curA = headA, *curB = headB;int lenA = 1;while(curA->next){curA = curA->next;lenA++;}int lenB = 1;while(curB->next){curB = curB->next;lenB++;}if(curA != curB){return NULL;}struct ListNode* longList = headA, *shortList = headB;if(lenA < lenB){longList = headB;shortList = headA;}int gap = abs(lenA - lenB);while(gap--){longList = longList->next;}while(longList != shortList){longList = longList->next;shortList = shortList->next;}return longList; }struct ListNode *detectCycle(struct ListNode *head) {struct ListNode* slow, *fast;slow = fast = head;while(fast && fast->next){slow = slow->next; fast = fast->next->next;if(slow == fast){struct ListNode* meet = slow;struct ListNode* next = meet->next;meet->next = NULL;struct ListNode* entry = getIntersectionNode(head,next);meet->next = next;return entry;}}return NULL;}
- 给定一个链表,每个结点包含一个额外增加的随机指针,该指针可以指向链表中的任何结点或空结点。
要求返回这个链表的深度拷贝。OJ链接
struct Node* copyRandomList(struct Node* head) {// 1.插入copy节点struct Node* cur = head;struct Node* copy;struct Node* next;while(cur){// 复制链接next = cur->next;copy = (struct Node*)malloc(sizeof(struct Node));copy->val = cur->val;cur->next = copy;copy->next = next;// 迭代往后走cur = next;}//2. 更新copy->randomcur = head;while(cur){copy = cur->next;if(cur->random == NULL)copy->random = NULL;elsecopy->random = cur->random->next;//迭代cur = copy->next;} //3. copy节点要解下来链接在一起,然后恢复原链表struct Node* copyHead = NULL, *copyTail = NULL;cur = head;while(cur){copy = cur->next;next = copy->next;//取节点尾插if(copyTail == NULL){copyHead = copyTail = copy;}else{copyTail->next = copy;copyTail = copyTail->next;}//恢复原链表链接cur->next = next;//迭代cur = next;}return copyHead; }
- 其他。ps:链表的题当前因为难度及知识面等等原因还不适合当前学习,下面有OJ链接。
Leetcode OJ链接 + 牛客 OJ链接