在链表中有许多经典的笔试面试题,这里就写几个常见的吧。
以下是头文件中的函数声明:
seqlink.h #include<stdio.h> #pragma once typedef char LinkNodeType; typedef struct LinkNode { LinkNodeType data; struct LinkNode* next; } LinkNode; // 初始化链表 void LinkListInit(LinkNode** node); void LinkListPrintChar(LinkNode* head,const char* msg);//打印 //尾插一个元素到链表中 void LinkListPushBack(LinkNode** head, LinkNodeType value); void LinkListPopBack(LinkNode** head);//尾删 void LinkListInsert(LinkNode*pos,LinkNodeType value); //新节点插入在pos之后 void LinkListEarse(LinkNode** head,LinkNode* pos);//删除指定位置 LinkNode* LinkListFind(LinkNode* head,LinkNodeType value);//查找位置 void LinkListInsertBef(LinkNode** head,LinkNode* pos,LinkNodeType value); //在pos之前插入元素 size_t LinkListSize(LinkNode* head);//元素个数 void LinkListReveserPrint(LinkNode* head);//逆序打印链表 LinkNode* LinkListReveser(LinkNode** head);//单链表逆置 void LinkListBubbleSort(LinkNode* head);//冒泡排序 LinkNode* LinkListMerge(LinkNode* head1, LinkNode* head2); //合并两个有序链表 LinkNode* HasCycle(LinkNode* head);//判断是否有环 LinkNode* FindMidNode(LinkNode* head); //找到中间节点 LinkNode* FindLastKNode(LinkNode* head, size_t K); //找到倒数第K个节点 void EraseLastKNode(LinkNode** head, size_t K); //删除倒数第K个节点 size_t GetCycleLen(LinkNode* head);//求环长度 // 如果链表带环, 求出环的入口 LinkNode* GetCycleEntry(LinkNode* head); LinkNode* HasCross(LinkNode* head1, LinkNode* head2);//链表相交,且不带环 LinkNode* JosephCycle(LinkNode* head, size_t food); //约瑟夫环 LinkNode* HasCrossWithCycle(LinkNode* head1,LinkNode* head2);//链表相交,可能带环
一、单链表逆序打印
这里的逆序打印采用的是递归的方法,
void LinkListReveserPrint(LinkNode* head) { if(head == NULL) { //空链表 return; } LinkListReveserPrint(head->next); printf("[%c|%p]\t",head->data,head); return; }
二、单链表的逆置
①:循环头插
这里分享一下思路
②:三指针循环
LinkNode* LinkListReveser(LinkNode** head) { if(head == NULL) { //error return NULL; } if(*head == NULL) { //error return NULL; } if((*head)->next == NULL) { printf("只有一个元素\n");//只有一个元素 return *head; } LinkNode* cur = *head; LinkNode* Next = *head; LinkNode* pre = NULL; while(cur) { Next = cur->next; cur->next = pre; pre = cur; cur = Next; } *head = pre; return *head; }
三、冒泡排序
void LinkListBubbleSort(LinkNode* head) { if(head == NULL) { //空链表 return; } LinkNode* cur = head; LinkNode* pre = NULL; while(cur != pre) { while(cur->next != pre) { if(cur->data > cur->next->data) { LinkNodeType tmp = cur->data; cur->data = cur->next->data; cur->next->data = tmp; } cur = cur->next; } pre = cur; } cur = head; }
四、合并两个有序链表
LinkNode* LinkListMerge(LinkNode* head1, LinkNode* head2) { if(head1 == NULL) { //空链表head1 return head2; } if(head2 == NULL) { //head2 == NULL return head1; } LinkNode* head = NULL; LinkNode* tail = NULL; LinkNode* cur1 = head1; LinkNode* cur2 = head2; if(cur1->data < cur2->data) { head = cur1; cur1 =cur1->next; } else { head = cur2; cur2 = cur2->next; } tail = head; while(cur1 != NULL && cur2 != NULL) { if(cur1->data < cur2->data) { tail->next = cur1; tail = cur1; cur1 = cur1->next; } else { tail->next = cur2; tail = cur2; cur2 = cur2->next; } } tail->next = (cur1 != NULL) ? cur1:cur2; return head; }
五、判读是否有环
1.遍历
将头指针保存,从头遍历,时间复杂度On^2 空间复杂度On
2.快慢指针
快指针走2步,慢指针走1步,当2个指针相遇,代表有环
注意:当环长为1,怎么走都不会相遇。
LinkNode* HasCycle(LinkNode* head) { if(head == NULL) { return NULL; } LinkNode* fast = head; LinkNode* slow = head; while(fast->next != NULL && fast->next->next != NULL) { fast = fast->next->next; slow = slow->next; if(slow == fast) { return fast; } } return NULL; }
六、找到中间节点、倒数第K个节点、删除倒数第K个节点
使用快慢指针的方法可以求解。
1.当快指针走到结尾,慢指针刚好到到中间节点。
2.先让快指针走K步,然后然慢指针和快指针开始同步走,当快指针到达结尾,慢指针就是倒数第K个节点
3.删除倒数第K个节点,首先求链表的长度,如果K刚好等于长度,则采用头删即可,若不是,则找到它之前的那个节点,就是倒数第K+1个节点。
LinkNode* FindMidNode(LinkNode* head) { if(head == NULL) { return NULL; } LinkNode* fast = head; LinkNode* slow = head; while(fast!= NULL) { if(fast == NULL) { return slow; } fast = fast->next->next; slow = slow->next; } }
LinkNode* FindLastKNode(LinkNode* head, size_t K) { if(head == NULL) { return NULL; } LinkNode* fast = head; LinkNode* slow = head; int i = 1; for(;i<=K;++i) { fast = fast->next; } while(fast->next != NULL) { fast = fast->next; slow = slow->next; } return slow; }
void EraseLastKNode(LinkNode** head, size_t K) { if(head == NULL) { return; } if(*head == NULL) { return; } LinkNode* pre = *head; LinkNode* cur = *head; size_t len = 0; while(cur != NULL) { len++; cur = cur->next; } if(K > len) { return; } if(K == len) { LinkListPopFourt(head); } int i = 0; for(;i < len-K;++i) { pre = pre->next; } LinkListEarse(head,pre); //pre->next = pre->next->next; //free(pre->next); }
七、约瑟夫环
前提条件:尾指向头。
1.从头开始,先将第K个节点删除,
2.从删除节点的下一个开始,找到之后的第K个节点删除
LinkNode* JosephCycle(LinkNode* head, size_t food) { if(head == NULL) { return NULL; } LinkNode* cur = head; while(cur->next != cur) { int i = 0; for (;i < food; ++i) { cur = cur->next; } LinkNode* node = cur->next; cur->data = node->data; cur->next = node->next; free(node); } return cur; }
八、判断链表是否带环、环长度、环入口
LinkNode* HasCycle(LinkNode* head)//是否带环 { if(head == NULL) { return NULL; } LinkNode* fast = head; LinkNode* slow = head; while(fast->next != NULL && fast->next->next != NULL) { fast = fast->next->next; slow = slow->next; if(slow == fast) { return fast; } } return NULL; }
当快慢指针相遇时,必然有环。可以定义一个指针,从相遇点的下一个节点开始走,并且计数。当返回到相遇点时,查看计数就知道环的长度
size_t GetCycleLen(LinkNode* head)//环长度 { if(head == NULL) { return 0; } LinkNode* fast = head; LinkNode* slow = head; while(fast->next != NULL && fast->next->next != NULL) { fast = fast->next->next; slow = slow->next; if(fast == slow) { size_t count = 0; LinkNode* cur = slow->next; while(cur != slow) { ++count; cur = cur->next; } return count; } } return 0; }
LinkNode* GetCycleEntry(LinkNode* head)//环入口 { if(head == NULL) { return NULL; } LinkNode* meet = HasCycle(head);//相遇点 LinkNode* slow = head;//慢指针从头开始走 LinkNode* fast = meet;//快指针从相遇点开始走 while(fast != slow) // 当两个指针相遇时,即入口点 { fast = fast->next; slow = slow->next; } return fast; }