单链表经典算法题
1.移除链表元素

1.1思路一:

代码如下:
typedef struct ListNode ListNode;
while(head!=NULL&&head->val==val)
{
struct ListNode*temp=head;
head=head->next;
free(temp);
}
if(head==NULL)
return NULL;
ListNode* pcur=head;
ListNode* prev=head;
ListNode* pnext=head;
while(pcur)
{
pnext=pcur->next;
if(pcur->val==val)
{
prev->next=pnext;
free(pcur);
}
else
{
prev=pcur;
}
pcur=pnext;
}
return head;
}
易错点:
1.头节点是val的情况未考虑到
2.pnext保存太晚
3.头处理后,注意链表删空的情况,返回NULL
4.注意prev只有在不删节点时才更新
1.2思路二:
找值不为val的节点,尾插到新链表中

代码如下:
typedef struct ListNode ListNode;
struct ListNode* removeElements(struct ListNode* head, int val) {
ListNode* pcur=head;
ListNode* NewHead,*NewTail;
NewHead=NewTail=NULL;
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;
}
易错点:
1. 最后一次把5这个节点拿到新链表进行尾插的时候,这个节点的next也拿下来了,所以必须要将NewTail的next置空
2. 注意链表为空的情况
2.反转链表

2.1思路一:
把链表的每个节点,依次头插到新链表的前面
typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head){
ListNode* NewHead=NULL;
ListNode* pcur=head;
while(pcur)
{
//提前保存当前节点的下一个节点
ListNode* pnext=pcur->next;
//头插
pcur->next=NewHead;
NewHead=pcur;
//继续往后遍历
pcur=pnext;
}
return NewHead;
2.2思路二:

typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head){
ListNode* n1,*n2,*n3;
//判空
if(head==NULL)
return head;
n1=NULL,n2=head,n3=n2->next;
while(n2)
{
n2->next=n1;
n1=n2;
n2=n3;
if(n3)
n3=n3->next;
}
return n1;
}
易错点:一定要判断链表是否为空
3.链表的中间节点

3.1思路一:
遍历,count记节点数,直接返回(count/2)节点的next节点
typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) {
int count=0;
ListNode* pcur=head;
while(pcur)
{
pcur=pcur->next;
count++;
}
int mid=count/2;
pcur=head;
for(int i=0;i<mid;i++)
{
pcur=pcur->next;
}
return pcur;
}
3.2思路二:
快慢指针
typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) {
ListNode* fast,*slow;
fast=slow=head;
while(fast&&fast->next)
{
fast=fast->next->next;
slow=slow->next;
}
return slow;
}
易错点:while里的fast与fast->next一定不能交换位置,因为如果fast为空,那么fast->next就是对空指针的解引用,代码一定会报错
4.合并两个有序链表

思路:创建新链表,遍历原链表,把较小的数拿到新链表进行尾插。
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
ListNode* l1=list1;
ListNode* l2=list2;
ListNode* NewHead,*NewTail;
NewHead=NewTail=NULL;
if(l1==NULL)
{
return l2;
}
if(l2==NULL)
{
return l1;
}
while(l1&&l2)
{
if(l1->val < l2->val)
{
if(NewHead==NULL)
{
NewHead=NewTail=l1;
}
else
{
NewTail->next=l1;
NewTail=NewTail->next;
}
l1=l1->next;
}
else
{
if(NewHead==NULL)
{
NewHead=NewTail=l2;
}
else
{
NewTail->next=l2;
NewTail=NewTail->next;
}
l2=l2->next;
}
}
//跳出循环,说明l1或l2走到空了
if(l1)
{
NewTail->next=l1;
}
if(l2)
{
NewTail->next=l2;
}
return NewHead;
}

但是这个代码存在重复,我们如何优化一下呢?
这里我们可以让链表不为空,用malloc动态申请一块空间:

typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
ListNode* l1=list1;
ListNode* l2=list2;
ListNode* NewHead,*NewTail;
NewHead=NewTail=(ListNode*)malloc(sizeof(ListNode));
if(l1==NULL)
{
return l2;
}
if(l2==NULL)
{
return l1;
}
while(l1&&l2)
{
if(l1->val < l2->val)
{
NewTail->next=l1;
NewTail=NewTail->next;
l1=l1->next;
}
else
{
NewTail->next=l2;
NewTail=NewTail->next;
l2=l2->next;
}
}
//跳出循环,说明l1或l2走到空了
if(l1)
{
NewTail->next=l1;
}
if(l2)
{
NewTail->next=l2;
}
ListNode* ret=NewHead->next;
free(NewHead);
NewHead=NULL;
return ret;
}
易错点:
1. 动态申请的空间必须要释放,释放前需要定义一个新变量保存NewHead->next的地址
2. 最后返回的值应该是NewHead的下一个节点,NewHead里面没有有效值,是系统随机分配的。
5.环形链表的约瑟夫问题

根据示例1,我们画下图进行分析:


根据题意,我们需要释放2节点,所以首先我们先创建两个变量prev与pcur,释放2之前先让prev->next指向pcur->next,然后再释放2,接着我们需要把编号1跟2挪动位置
循环此操作过程几次后得到如下图:

接下来一起写代码:
第一步:创建带环链表
这一步我们封装一个函数creatCircle。
首先要先创建第一个节点,这里我们再封装一个函数BuyNode,然后我们将数据尾插到链表中。最后首尾相连,链表成环

注意:该函数的返回值取决于我们接下来要怎么使用这个链表,由于返回尾节点ptail可以同时直接访问头节点,所以我们返回的是ptail

第二步: 计数
我们用prev与pcur去接收ptail与ptail->next,然后我们用count来计数,count初始值为1,当count与m相等的时候,需要销毁pcur节点,否则prev与pcur各后移一位。

因为删到最后pcur->next会指向自己,如下图

所以可得循环while里的条件。

6.分割链表

6.1思路一:
在原链表上进行修改,若pcur节点的值小于x,往后走;
若pcur节点的值大于等于x,尾插在原链表后,删除旧节点。

typedef struct ListNode ListNode;
struct ListNode* partition(struct ListNode* head, int x) {
if(head==NULL||head->next==NULL)
{
return head;
}
ListNode phead;
phead.next=head;
ListNode* prev=&phead;
ListNode* pcur=head;
ListNode* ptail=head;
while(ptail->next)
{
ptail=ptail->next;
}
ListNode* newPtail=ptail;
while(pcur!=ptail)
{
if(pcur->val<x)
{
prev=pcur;
pcur=pcur->next;
}
else
{
ListNode* temp=pcur;
pcur=pcur->next;
prev->next=pcur;
newPtail->next=temp;
newPtail=newPtail->next;
newPtail->next=NULL;
}
}
return phead.next;
}
6.2思路二:
创建新链表,遍历原链表,若pcur节点的值小于x,头插在新链表中;
若pcur节点的值大于或等于x,尾插在新链表中。

typedef struct ListNode ListNode;
struct ListNode* partition(struct ListNode* head, int x) {
if(head==NULL||head->next==NULL)
{
return head;
}
ListNode newHead;
newHead.next=NULL;
ListNode* newTail=&newHead;
ListNode* pcur=head;
while(pcur)
{
ListNode* nextNode=pcur->next;
if(pcur->val<x)
{
if(newHead.next==NULL)
{
newTail=pcur;
}
pcur->next=newHead.next;
newHead.next=pcur;
}
else
{
newTail->next=pcur;
newTail=newTail->next;
}
pcur=nextNode;
}
newTail->next=NULL;
return newHead.next;
}
易错点:
1. newTail需要在第一次进行的是头插的时候更新
2. 未提前保存nextNode
3. 尾部未置空
6.3思路三:
创建两个新链表,一个小链表,一个大链表


然后我们将小链表的尾节点与大链表的第一个节点首尾相连

易错点:
1. 原链表中5的next指针指向的是2,如果不做处理,就会造成死循环,如下图:

2. 一定要先将greatertail的next指针进行初始化,再将小链表尾节点与大链表的第一个节点相连
typedef struct ListNode ListNode;
struct ListNode* partition(struct ListNode* head, int x) {
if(head==NULL||head->next==NULL)
{
return head;
}
ListNode* lessHead,*lessTail;
ListNode* greaterHead,*greaterTail;
lessHead=lessTail=(ListNode*)malloc(sizeof(ListNode));
greaterHead=greaterTail=(ListNode*)malloc(sizeof(ListNode));
ListNode* pcur=head;
while(pcur)
{
if(pcur->val<x)
{
lessTail->next=pcur;
lessTail=pcur;
}
else
{
greaterTail->next=pcur;
greaterTail=pcur;
}
pcur=pcur->next;
}
greaterTail->next=NULL; //对next指针初始化+避免代码出现死循环
lessTail->next=greaterHead->next;
ListNode* ret=lessHead->next;
free(lessHead);
free(greaterHead);
lessHead=greaterHead=NULL;
return ret;
}
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)