文章目录
分享几道LeetCode的关于单链表的OJ题,是笔试中的高频考题哦。
一、移除链表元素
先看题目描述:

思路一:遍历链表找值为val的节点,执行删除指定位置的节点的操作。由于用到了嵌套的while循环,所以时间复杂度为O(n²)。
c
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val) {
ListNode* pcur = head;
while(pcur)
{
if(pcur->val == val)
{
//要删除的是头节点
if(pcur == head)
{
head = head->next;
free(pcur);
pcur = head;
}
else
{
//要删除的不是头节点,从头找前驱节点
ListNode* prev = head;
while(prev->next != pcur)
{
prev = prev->next;
}
prev->next = pcur->next;
free(pcur);
pcur = prev->next;
}
}
else
{
pcur = pcur->next;
}
}
return head;
}
思路二:创建新链表,遍历原链表,将值不为val的节点拿下来尾插。时间复杂度为O(n)。
c
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val) {
ListNode* newHead,*newTail;
newHead = newTail = NULL;
ListNode* pcur = head;
while(pcur)
{
if(pcur->val != val)
{
if(newHead == NULL)
{
newHead = newTail = pcur;
}
else
{
newTail->next = pcur;
newTail = newTail->next;
}
}
pcur = pcur->next;
}
if(newTail)
{
newTail->next = NULL;
}
return newHead;
}
二、反转链表
题目描述:


思路一:创建新链表,遍历原链表,将原链表中的每个节点头插到新链表中。
c
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) {
ListNode* newHead,*newTail;
newHead = newTail = NULL;
ListNode* pcur = head;
while(pcur)
{
ListNode* next = pcur->next;
if(newHead == NULL)
{
newHead = newTail = pcur;
newTail->next = NULL;
}
else
{
pcur->next = newHead;
newHead = pcur;
}
pcur = next;
}
return newHead;
}
以上思路的代码优化,(头插法天然就支持空的 newHead 场景):
c
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) {
ListNode* newHead = NULL;
ListNode* pcur = head;
while(pcur)
{
ListNode* next = pcur->next;
pcur->next = newHead;
newHead = pcur;
pcur = next;
}
return newHead;
}
思路二:创建三个指针。
c
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) {
if(head == NULL)
{
return head;
}
ListNode* n1, *n2, *n3;
n1 = NULL, n2 = head, n3 = n2->next;
while(n2)
{
n2->next = n1;
n1 = n2;
n2 = n3;
if(n3)
n3 = n3->next;
}
return n1;
}
三、链表的中间结点
题目描述:
思路一:求链表的总长度,总长度除2求中间节点的位置,返回中间位置的节点。
c
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) {
ListNode* pcur = head;
int count = 0,step = 0;
while(pcur)
{
count++;
pcur = pcur->next;
}
pcur = head;
step = count/2;
while(step--)
{
pcur = pcur->next;
}
return pcur;
}
思路二:快慢指针。进入循环后,慢指针每次走一步,快指针每次走两步。

在奇数个节点中,fast->next为空时,slow走到了中间节点的位置;在偶数个节点中,fast为空时,slow走到了中间节点的位置。
让我们把思路转换为代码,注意这里循环结束的条件是fast 和fast->next同时不为NULL,因为只要有一个为NULL,就要结束循环,二者是 || 的关系,而要进入循环就得是(fast || fast->next)的取反操作,结果为(fast && fast->next):
另外,这里fast和fast->next不能调换位置,也就是在while条件中,fast->next不能写在了fast前面。因为如果fast走两步后走到了NULL,再上来先判断fast->next,属于对NULL指针解引用。
c
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) {
ListNode* slow = head;
ListNode* fast = head;
while(fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
四、合并两个有序链表
题目描述:


思路一:将节点放到指定位置之前。将一条链表作为被插入链,遍历另一条链表的每一个节点,比较这条链表当前节点的值和被插入链中节点值的大小,找到被插入链中第一个值比当前节点值大的节点,将这个节点插入到被插入链中这个更大的值的节点的前面。(dummy:占位符,在这里是哨兵位节点)
c
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
ListNode* dummy = (ListNode*)malloc(sizeof(ListNode));
dummy->next = list1;
ListNode* prev = dummy;
ListNode* curl2 = list2;
while(curl2)
{
ListNode* nextl2 = curl2->next;
ListNode* pos = prev->next;
while(pos && pos->val < curl2->val)
{
prev = pos;
pos = pos->next;
}
curl2->next = pos;
prev->next = curl2;
curl2 = nextl2;
}
ListNode* ans = dummy->next;
free(dummy);
dummy = NULL;
return ans;
}
思路二:创建新的空链表,从头开始比较两个链表中当前位置的节点的大小,将节点值更小的节点尾插进新的链表中,往后遍历链表,直到其中一条链表走到NULL,将另一条链表中剩下的所有节点直接一次性尾插进新链表中。时间复杂度为O(n)
c
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
if(list1 == NULL)
{
return list2;
}
if(list2 == NULL)
{
return list1;
}
ListNode* newHead,*newTail;
newHead = newTail = NULL;
ListNode* l1 = list1;
ListNode* l2 = list2;
while(l1 && l2)
{
if(l1->val < l2->val)
{
if(newHead == NULL)
{
newHead = newTail = l1;
}
else
{
newTail->next = l1;
newTail = l1;
}
l1 = l1->next;
}
else
{
if(newHead == NULL)
{
newHead = newTail = l2;
}
else
{
newTail->next = l2;
newTail = l2;
}
l2 = l2->next;
}
}
if(l1)
{
newTail ->next = l1;
}
if(l2)
{
newTail->next = l2;
}
return newHead;
}
以上代码在while循环里的if-else各自的主体部分,有大量重复代码,让我们来做下优化。创建一个头结点让头指针指向就可以省去if(newHead == NULL)这部分判空了,也就可以省去好几句的重复代码。
但记住,最后需要释放malloc出来的这个头节点。
c
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
if(list1 == NULL)
{
return list2;
}
if(list2 == NULL)
{
return list1;
}
ListNode* newHead,*newTail;
newHead = newTail = (ListNode*)malloc(sizeof(ListNode));
ListNode* l1 = list1;
ListNode* l2 = list2;
while(l1 && l2)
{
if(l1->val < l2->val)
{
newTail->next = l1;
newTail = l1;
l1 = l1->next;
}
else
{
newTail->next = l2;
newTail = l2;
l2 = l2->next;
}
}
if(l1)
{
newTail ->next = l1;
}
if(l2)
{
newTail->next = l2;
}
ListNode* retNode = newHead->next;
free(newHead);
newHead = NULL;
return retNode;
}