在链表中有许多经典的笔试面试题,这里就写几个常见的吧。
以下是头文件中的函数声明:
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;
}