要做单链表的面试题,首先得要学会实现单链表,无头单向非循环链表的实现,有需要的童鞋还可以看一下带头双向循环链表的实现
- 从尾到头打印单链表
思路:采用递归的方法,当plist->next指向的节点为NULL时,递归停止,开始反向依次输出链表上的节点
void TailToHead(ListNode *plist)
{
assert(plist);
if(plist == NULL)
return;
TailToHead(plist->next);
printf("%d->",plist->data);
}
- 删除一个无头单链表的非尾节点(不能遍历链表)
思路:
- 将要删除的节点定义为cur,要删除节点的下一节点定义为next;
- 将要删除节点的下一节点的data赋给要删除的节点;
- 将要删除节点的下一节点的next赋给删除节点的下一节点的next;
- free掉要删除节点的下一节点
图解:
void DeleteNoHead(ListNode* pos)
{
assert(pos);
if(pos == NULL)
return;
ListNode* cur = pos;
ListNode* next = pos->next;
cur->data = next->data;
cur->next = next->next;
free(next);
next = NULL;
}
感悟:要求删除3,实际删除的是5而不是3,原因在于:在执行删除操作之前已经将5所在的节点赋给3,而所谓的删除不过是将要删除的节点用下一节点覆盖而已
- 在无头单链表的一个节点前插入一个节点(不能遍历链表)
思路:
- 创建一个值为 pos节点的值 的新节点;
- 把新节点插入到pos的后面;
- 把要插入的节点的值赋给pos
void InsertNoHead(ListNode* pos, LDataType data)
{
if (pos == NULL)
return;
ListNode* node = BuyListNode(pos->data);
pos->next = node;
node->next = pos->next;
pos->data = data;
}
- 单链表实现约瑟夫环(JosephCircle)
首先看一下什么约瑟夫环问题?
据说著名犹太历史学家 Josephus有过以下的故事:在罗马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。然而Josephus 和他的朋友并不想遵从。首先从一个人开始,越过k-2个人(因为第一个人已经被越过),并杀掉第k个人。接着,再越过k-1个人,并杀掉第k个人。这个过程沿着圆圈一直进行,直到最终只剩下一个人留下,这个人就可以继续活着。问题是,给定了和,一开始要站在什么地方才能避免被处决?Josephus要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,于是逃过了这场死亡游戏。
思路:
- 把头指针定义为cur,遍历链表
- 设定k值,循环到k值时,记录当前位置的下一个节点为next
- 把next节点非给cur节点,释放next节点
ListNode* JosephCircle(ListNode* head, LDataType k)
{
ListNode* cur = head;
while (cur->next)
{
while (k--)
{
cur = cur->next;
}
ListNode* next = cur->next;
cur->data = next->data;
cur->next = next->next;
free(next);
}
return cur;
}
- 逆置/反转单链表
思路:
- 定义三个节点,分别为当前节点,当前节点的下一个节点,逆置后新链表的头节点
- 把头结点的 next 置空,再逐个头插入新链表
void Reverse(ListNode* head)
{
ListNode* cur = head;
ListNode *_next = cur->next;
ListNode *newHead = NULL;
cur->next = NULL;
while (_next != NULL)
{
newHead = _next;
newHead->next = cur->next;
cur->next = newHead;
_next = _next->next;
}
}
- 单链表排序(冒泡排序)
思路:
- 单链表排序和普通排序的思路是一致的,遍历链表,交换值
void BubbleSort(ListNode* head)
{
ListNode* cur = head;
ListNode* _next = cur->next;
for (; cur != NULL; cur = _next)
{
size_t flag = 0;
while (_next)
{
if (cur->data > _next->data)
{
size_t tmp = cur->data;
cur->data = _next->data;
_next->data = tmp;
flag++;
}
_next = _next->next;
cur = cur->next;
}
if (flag == 1)
break;
}
}
- 合并两个有序链表,合并后依然有序
思路:
- 新建一个节点作为合并完的链表的头节点
- 若其中一条链表为空,直接返回另一条链表
- 找到两条链表头节点中较小的作为新链表的头节点
- 遍历两条链表,按序将小节点连接在新链表上
- 如果在遍历过程中,其中一条链表已经遍历完,则直接把另一条链表的剩余部分连接在新链表上
ListNode* ListMerge(ListNode* list1, ListNode* list2)
{
ListNode* list = NULL;//新链表的头节点
assert(list1);
assert(list2);
//如果其中一条链表为空,直接返回另一条链表
if (list1 == NULL)
return list2;
if (list2 == NULL)
return list1;
//为合并后的新链表找到一个头节点
if (list1->data > list2->data)
{
list = list2;
list2 = list2->next;
}
else
{
list = list1;
list1 = list1->next;
}
ListNode* cur = list;
//找到两条链表中 较小的节点连接在新链表上
while (list1 && list2)
{
if (list1->data > list2->data)
{
cur->next = list2;
cur = cur->next;
list2 = list2->next;
}
else
{
cur->next = list1;
cur = cur->next;
list1 = list1->next;
}
}
//如果在遍历过程中,其中一条链表已经遍历完,
//则直接把另一条链表的剩余部分连接在新链表上
if (list1->next == NULL)
cur->next = list2;
if (list2->next == NULL)
cur->next = list2;
return list;
}
- 查找单链表的中间节点,要求只能遍历一次链表
思路:
- 定义两个节点,一个快节点一个慢节点
- 快的每次走两步,慢的走一步
- 当快节点的next为空时,返回慢节点
ListNode* FindMidNode(ListNode* head)
{
ListNode* slow = head;
ListNode* fast = head;
assert(head);
while (fast && fast->next && fast->next->next)
{
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
- 查找单链表的倒数第K个节点,要求只能遍历一次链表
思路:
- 定义两个节点,一个快节点一个慢节点
- 先让快节点走K步,然后两个节点一起走
- 当快节点的next为空时,返回慢节点
ListNode* FindKNode(ListNode* head, LDataType k)
{
ListNode* fast = head;
ListNode* slow = head;
if (head == NULL)
return NULL;
while (fast && fast->next)
{
if(k--)
{
fast = fast->next;
}
fast = fast->next;
slow = slow->next;
}
return slow;
}
- 删除链表的倒数第K个节点
思路:
- 首先找到倒数第K个节点
- 遍历链表找到倒数第K个节点的前一个节点为cur
- 把cur->next指向倒数第K个节点的下一个节点,释放倒数第K个节点
void DeleteKNode(ListNode* head, LDataType k)
{
ListNode* kNode = FindKNode(head, k);
ListNode* cur = head;
while (cur->next != kNode)
{
cur = cur->next;
}
cur->next = kNode->next;
free(kNode);
}
- 判断单链表是否带环
思路:
- 定义快慢指针,快指针走两步,慢指针走一步
- 当快指针等于慢指针时,说明链表带环,返回当前位置的节点
- 如果链表不带环,快指针先到头,此时结束循环,返回空
ListNode* IsCycle(ListNode* head)
{
ListNode* fast = head;
ListNode* slow = head;
//链表为空 | 链表中只有一个节点
if (head == NULL || head->next == NULL)
return NULL;
//快节点到头
while (fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
if (fast == slow)
{
return slow;
}
}
return NULL;
}
若带环,求入口点
思路:
从链表头结点到入口节点的长度 = 相遇节点到入口节点的长度
推导过程:
设环的长度为:C
从链表头结点到入口节点的长度为:L
入口节点到相遇节点的长度为:X
快指针在环中走的圈数:n
快指针走的步数是:L+nC+X
慢指针走的步数是:L+X
根据快指针走的步数是慢指针的两倍可得关系:L+nC+X = 2(L+X)
化简后可得:nC = L+X (n=1,2,3…)
令n = 1,则有C = L + X,即从链表头结点到入口节点的长度 = 相遇节点到入口节点的长度
ListNode* GetEntry(ListNode* head, ListNode* MeetNode)
{
while (head != MeetNode)
{
head = head->next;
MeetNode = MeetNode->next;
}
return MeetNode;
}
求环的长度
思路:
从相遇节点的下一节点cur开始,遍历环,只要cur不等于MeetNode,长度 + 1
LDataType CycleLength(ListNode* MeetNode)
{
ListNode* cur = MeetNode->next;
LDataType length = 1;
while (cur != MeetNode)
{
cur = cur->next;
++length;
}
return length;
}
- 判断两个链表是否相交(链表不带环)
思路:
遍历两个链表到各自结束,当最后一个节点相等时,表示两个链表相交
LDataType IsCross_NoCycle(ListNode* list1, ListNode* list2)
{
if (list1 == NULL || list2 == NULL)
return -1;
while(list1->next)
{
list1 = list1->next;
}
while(list2->next)
{
list2 = list2->next;
}
if (list1 == list2)
return 1;
}
若相交,求交点
思路:
- 先求出两个链表各自的长度
- 求出两个链表长度的差值为gap
- 长的那个,先走gap步,然后两个一起走
- 当两者相等时,返回相等的这个节点,即交点
ListNode* GetNode_NoCycle(ListNode* list1, ListNode* list2)
{
ListNode* Llist = list1;
ListNode* Slist = list2;
LDataType length1 = 0;
LDataType length2 = 0;
//求两个链表各自的长度
while (Llist->next)
{
length1++;
}
while (Slist->next)
{
length2++;
}
//找到较长的那个链表
if (length1 > length2)
{
Llist = list1;
Slist = list2;
}
//两个链表长度的差值
LDataType gap = abs(length1 - length2);
//长的先走gap步
while (gap--)
{
Llist = Llist->next;
}
//两个一起走,相等时退出循环,此时的节点即交点
while (Llist != Slist)
{
Llist = Llist->next;
Slist = Slist->next;
}
return Slist;
}
- 判断两个链表是否相交,若相交,求交点(链表可能带环)
我们知道,如果两个链表相交,则意味着它们最后的NULL指针指向的是同一个地方,即末节点必然相交,所以当其中一个链表有环时,这两个链表必然不相交。因为当链表有环时,环必须是相交的,否则不能满足链表相交的前提(尾节点相交)。
下面看一下两个链表相交的几种情况:
思路:
- 先判断链表是否带环
- 有一个不带环,则不相交
- 两个都带环,入口点相同时,先把环的部分切掉,就变成了两个不带环链表求交点的问题
- 两个都带环,入口点不同时,从第一条链表快慢指针相遇的节点开始,遍历环;如果找到第二条链表快慢指针相遇的节点,表示共用环,返回任一入口点即可(事实上,此时这个环上的任一节点都可作为相交点)
ListNode* IsCross_Cycle(ListNode* list1, ListNode* list2)
{
//判断链表是否带环
ListNode* MeetNode1 = IsCycle(list1);
ListNode* MeetNode2 = IsCycle(list2);
//其中一个不带环,不相交
if (MeetNode1 == NULL || MeetNode2 == NULL)
{
return NULL;
}
//两个都带环,求出各自的入口点
ListNode* entry1 = GetEntry(list1, MeetNode1);
ListNode* entry2 = GetEntry(list2, MeetNode2);
//入口点相同
if (entry1 == entry2)
{
//先把环切掉,直接求出交点
entry1->next = NULL;
entry2->next = NULL;
return GetNodeCross(list1, list2);
}
//入口点不同,从MeetNode1开始遍历链表,如果找到和MeetNode2相等的节点,
//则证明共用环,即两个链表相交,此时返回任一入口点即可
ListNode* cur = MeetNode1->next;
while(cur!=MeetNode1 && cur != MeetNode2)
{
cur = cur->next;
}
if (cur = MeetNode2)
return entry1;
return NULL;
}