本篇博客整理了一些常见的链表oj题(以下所有oj题均出自leetcode平台),用于交流和学习。下面将通过C实现这些oj题。
🌏删除链表指定节点
给定一个链表的首节点head和一个整数val,删除链表中所有满足node.val == val的节点,并返回新的首节点。示例如下:
基本思路:创建一个哨兵位头节点,遍历原链表所有节点,若节点值不等于val,则将该节点尾插进新链表;若节点值等于val,free该节点后跳向下一个节点。
按照该思路,大致作图如下:
代码实现:
struct ListNode* removeElements(struct ListNode* head, int val){
struct ListNode* newHead = malloc(sizeof (struct ListNode));
struct ListNode* tail = newHead;//维护新链表尾节点 方便尾插
struct ListNode* cur = head;
while (cur)
{
//先存一份next避免节点free后无法继续遍历
struct ListNode* next = cur->next;
if (cur->val == val)
{
free(cur);
}
else
{
tail->next = cur;
tail = tail->next;
}
cur = next;
}
tail->next = NULL;//注意这里要置空
struct ListNode* next = newHead->next;
free(newHead);
return next;
}
注意:若链表为空或链表最后一个节点的val等于指定的val时,新链表的尾节点链向的是一个野指针,因此在最后别忘了将尾节点的next置为NULL。
🌏逆序链表
给定一个链表的首节点head,逆序该链表,并返回逆序后链表的首节点。示例如下:
基本思路:创建一个空的新链表,遍历原链表,将原链表的节点依次头插进新链表。
由于此题思路并不复杂,所以这里没有选择作图。
代码实现:
struct ListNode* reverseList(struct ListNode* head){
struct ListNode* newHead = NULL;
struct ListNode* cur = head;
while (cur)
{
//先存一份next避免当前节点头插进新链表后无法继续遍历
struct ListNode* next = cur->next;
cur->next = newHead;
newHead = cur;
cur = next;
}
return newHead;
}
🌏链表的中间节点
给定一个首节点为head的非空单链表,返回链表的中间节点。如果有两个中间节点,则返回第二个中间节点。示例如下:
示例1:
输入:[1,2,3,4,5]
输出:[3,4,5]
示例2:
输入:[1,2,3,4,5,6]
输出:[4,5,6]
由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。
思路一:先遍历一遍链表统计节点个数,节点数除以2就得到了中间节点的索引,再从链表首节点开始走索引步后,该节点即为中间节点。
代码实现:
struct ListNode* middleNode(struct ListNode* head){
struct ListNode* cur = head;
int count = 0;
while (cur)
{
count++;
cur = cur->next;
}
count /= 2;//获取中间节点的索引
cur = head;
while (count)//从首节点开始走索引步
{
cur = cur->next;
count--;
}
return cur;
}
思路二:使用快慢指针,慢指针一次走一步,快指针一次走两步,当快指针为NULL或快指针的next为NULL时,慢指针指向的节点即为中间节点。
按照该思路,大致作图如下:
代码实现:
struct ListNode* middleNode(struct ListNode* head){
struct ListNode* slow, *fast;
slow = fast = head;
while (fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
🌏合并两个有序链表
将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的(即新链表的节点为两个原链表的节点)。示例如下:
基本思路:创建一个哨兵位头节点,先同时遍历两个链表,将两个链表中val较小的节点尾插到新链表后跳向该链表较小节点的下一个节点,继续比较两个链表的节点直到其中一个链表走到NULL。因为每次比较后只是将其中一个链表的节点尾插到新链表,所以两个链表不可能同时走到NULL,此时其中一个链表还留有节点,需要将该链表剩下的所有节点尾插到新链表。
按照该思路,大致作图如下:
代码实现:
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
if (list1 == NULL && list2 == NULL)//保证至少一个链表不为空
return NULL;
struct ListNode* head = malloc(sizeof (struct ListNode));
struct ListNode* tail = head;
//两个链表同时遍历直到一个链表走到NULL
while (list1 && list2)
{
if (list1->val < list2->val)
{
tail->next = list1;
tail = tail->next;
list1 = list1->next;
}
else
{
tail->next = list2;
tail = tail->next;
list2 = list2->next;
}
}
//将其中一个链表剩下的节点尾插到新链表
if (list1)
tail->next = list1;
else
tail->next = list2;
struct ListNode* next = head->next;
free(head);
return next;
}
🌏相交链表
给定两个单链表的首节点headA和headB,找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回NULL。
示例1:
相交的起始节点为‘8’
示例2:
相交的起始节点为‘2’
示例3:
两个链表不相交,返回NULL
注意:当两个链表在某个节点相交后,不会像下图中左边那样,相交后分散;而是会像下图中右边那样,相交后汇集到两个链表共同的节点。这是因为链表的节点只能“多链一”而不能“一链多”,相交的起始节点不可能再同时向后链向两个节点,所以两个链表在相交后不会分散。由此我们能够得出两个链表相交的条件就是“两个链表的尾节点相同”。
基本思路:先判断两个链表尾节点是否相同来确定链表是否相交,不相交则直接返回NULL。然后分别遍历两个链表统计各自的节点数,两个链表的节点数相减得到差值。再让节点数较多的链表先走差值步,之后两个链表同时向后走,直到两个指针指向的节点地址相同,该节点即为相交的起始节点。
按照该思路,大致作图如下:
代码实现:
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
if (headA == NULL || headB == NULL)
return NULL;
struct ListNode* curA = headA, *curB = headB;
int countA = 0, countB = 0;
//因为最后是用两个链表节点数的差值 所以不计入尾节点也一样
while (curA->next)
{
countA++;
curA = curA->next;
}
while (curB->next)
{
countB++;
curB = curB->next;
}
if (curA != curB)//判断两个链表尾节点地址是否相同
return NULL;
//到这里两个链表必然相交
struct ListNode* longList = headA, *shortList = headB;
if (countA < countB)
{
longList = headB;
shortList = headA;
}
int gap = abs(countA - countB);
//长链表先走差值步
while (gap)
{
longList = longList->next;
gap--;
}
//找到地址相同的节点即为相交的起始节点
while (longList != shortList)
{
longList = longList->next;
shortList = shortList->next;
}
return longList;
}