前言
之前我们介绍了链表的知识,本篇博客我们来看一些关于单链表的OJ算法题目,大家可以通过这些题目来加深对单链表的理解,如果你对本篇内容感兴趣,请继续往下阅读,下面进入正文部分。
题目1 移除链表元素
这道题有两种思路,第一种思路:我们找出链表中等于val值得节点。然后将他们删除掉;第二种思路:我们创建一个新链表,找出值不等于val的节点,然后将它们尾插到新链表中。
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)//找值不为val的节点
{
if (newHead == NULL)//链表为空
{
newHead = newTail = pcur;
}
else//链表不为空
{
newTail->next = pcur;
newTail = newTail->next;
}
}
pcur = pcur->next;
}
if (newTail)//
{
newTail->next = NULL;
}
return newHead;
}
这里大家注意,在我们执行完所述操作后,要对新链表的尾节点进行判断,如果它为空,说明原链表就是空链表,那么我们直接返回newHead,即NULL。
题目2 反转链表
本题我们有两种思路,第一种思路大家比较容易想到,就是创建一个新链表,然后我们遍历原链表,将原链表中的元素依次头插到新链表中,这样就可以实现反转操作;这种思路比较好想,但是代码可能比较长,效率不一定高;下面还有第二种思路,比较难想,但是代码很简单,即我们创建三个指针,n1指向NULL,n2指向链表头节点,n3指向头节点的下一个节点,然后进行指针的变换,具体大家来看代码。
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;
}
题目3 链表的中间节点
本题看起来挺麻烦的,但是实际上是一道比较简单的题目,我们只需要运用快慢指针,就可以解决这个问题,所谓快慢指针,顾名思义就是创建两个指针,慢指针一次走一步,快指针一次走两步,这样当快指针走到链表结尾时,慢指针的位置就刚好是中间位置。
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;
}
这里还有一个细节问题,就是while循环的结束条件可以改为(fast->next&&fast)吗?
答案当然是不可以,因为如果是偶数个节点的链表,那么fast将为NULL,第一个条件就存在对空指针进行解引用的操作,此时代码会报错。
题目4 合并两个有序链表
本题要求我们合并两个有序链表,我们之前接触过合并两个有序数组,其实在这里思路是一样的,我们要创建新链表,遍历原链表,将值小的节点拿到新链表中进行尾插。
/**
* 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* l1=list1;
ListNode* l2=list2;
ListNode* newhead,*newtail;
newhead=newtail=(ListNode*)malloc(sizeof(ListNode));
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;
}
}
if(l1)
{
newtail->next=l1;
}
if(l2)
{
newtail->next=l2;
}
ListNode* ret=newhead->next;
free(newhead);
newhead=NULL;
return ret;
}
这里大家注意,我们创建新链表时,并没有向之前那样将头尾置为NULL,而是为它们申请了一块儿空间,相当于创建了一个节点,这样做可以避免新链表为空的情况,进而简化代码,当然我们这样做,申请了空间用完后,就需要进行空间的释放,其实我们新创建的这个节点叫做“哨兵节点”,我们后面会学到链表的分类,这类链表就叫做带头链表。
这里还有一个小问题,可能有同学会对这里存在疑问,如果l2是比较长的一个链表,那么会不会丢失一些数据?按照上面代码,是一个一个节点往下拿的;
其实这种情况是不会发生的,因为链表本身就是连接在一起的,上面一个一个操作是因为要比较大小,而比较完后,我们只需要将不为空的链表直接连接到新链表后即可,后面没有参与比较的数据会被一次性拿下来,不会存在数据丢失的问题。
题目5 环形链表的约瑟夫问题
本题是经典的约瑟夫问题,解决它的思路在于第一步我们要创建带环链表,第二步我们进行计数。下面我们来看具体代码。
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param n int整型
* @param m int整型
* @return int整型
*/
typedef struct ListNode ListNode;
//创建节点
ListNode* buyNode(int x)
{
ListNode* node=(ListNode*)malloc(sizeof(ListNode));
if(node==NULL)
{
exit(1);
}
node->val=x;
node->next=NULL;
return node;
}
//创建带环链表
ListNode* creatCircle(int n)
{
//先创建第一个节点
ListNode* phead=buyNode(1);
ListNode* ptail=phead;
for(int i=2;i<=n;i++)
{
ptail->next=buyNode(i);
ptail=ptail->next;
}
//首尾相连,链表成环
ptail->next=phead;
return ptail;
}
int ysf(int n, int m )
{
//1.根据n创建带环链表
ListNode* prev=creatCircle(n);
ListNode* pcur=prev->next;
int count =1;
//当链表中只有一个节点,循环停止
while(pcur->next!=pcur)
{
if(count==m)
{
//销毁pcur节点
prev->next=pcur->next;
free(pcur);
pcur=prev->next;
count=1;
}
else
{
//此时不需要销毁节点
prev=pcur;
pcur=pcur->next;
count++;
}
}
//此时剩下的一个节点就是要返回的节点值
return pcur->val;
}
题目6 分割链表
本道题相较于上面几道题,难度稍微要大一些,但是大家也不必担心,这道题我们还是需要创建新链表,只不过这次我们要创建两个新链表,一个小链表,一个大链表;这里顾名思义,小链表用来存储值小于x的节点,大链表用来存储值大于或等于x的节点;存储好后,我们将小链表的尾节点与大链表的第一个节点首尾相连。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode* partition(struct ListNode* head, int x)
{
if(head==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=lesstail->next;
}
else
{
//尾插到大链表中
greatertail->next=pcur;
greatertail=greatertail->next;
}
pcur=pcur->next;
}
//修改大链表中next指针的指向
greatertail->next=NULL;//避免产生死循环以及next指针初始化的问题
lesstail->next=greaterhead->next;//小链表的尾节点与大链表的第一个有效节点首尾相连
ListNode* ret=lesshead->next;
free(lesshead);
free(greaterhead);
lesshead=greaterhead=NULL;
return ret;
}
总结
本篇博客为大家介绍了链表的一些OJ题目,难度由易到难,如果你正在学习链表,那么我相信本篇本章会对你有所帮助,大家要重点掌握解题的算法思想,在后面遇到相似的题目可以运用起来,最后,希望本文内容能够为大家带来帮助,感谢阅读!