思路1:使用结构体指针 cur 遍历链表,遇到值为 val 时删除,删除之前需要判断是头删还是正常的删除,头删需要改变头指针;
正常的删除需要 cur(待删除节点)和 cur 前面一个节点 prev ,让prev 指向 cur 的下一个节点,然后释放掉 cur节点,再让 cur 指针指向现在 prev 指向的下一个节点(原来 cur 指向的下一个节点)。
那么如何找到 prev ?每一次遍历结束先将 cur 赋给 prev,cur 再往后移动一个节点,这样下一次遍历时,prev 就是 cur 的上一个节点。
问题:修改头指针不应该传头指针的地址(二级指针)吗?为什么这里没有?
因为函数结束返回新的头节点,因此本题中可以不传二级指针(通常不推荐)。
实现:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* removeElements(struct ListNode* head, int val)
{
struct ListNode* prev=NULL;
struct ListNode* cur=head;
while(cur)
{
//遇到val,需要删除
if(cur->val==val)
{
//判断是否为头删
if(cur==head)
{
//头指针指向第二个节点
head=cur->next;
//释放第一个节点
free(cur);
//第二个节点成为头节点
cur=head;
}
//正常删除
else
{
//cur的上一个节点指向指向cur的下一个节点
prev->next=cur->next;
//释放掉cur节点
free(cur);
//cur指针指向原来cur的下一个节点
cur=prev->next;
}
}
else
{
//记录当前cur为上一个节点prev
prev=cur;
//cur向下一个节点移动
cur=cur->next;
}
}
return head;
}
思路2:
定义一个 cur 指针遍历原链表,把不是 val 的节点,尾插到头节点为 newhead 的新链表,是 val 的节点,就释放掉。
释放的过程为先定义一个 del 节点来记录待删除的节点,然后 cur 往后移,释放 del 节点。
这样的做法势必导致时间复杂度的增加,因为每次尾插都要查找尾节点,如何解决呢?
定义一个尾指针 tail 来记录尾节点,cur 完成遍历后不能忘了将 tail 指向空,还需要考虑到如果原链表为空,那么 newhead 就为空,tail 也为空,因此需要限制 tail 不为空的时候才能将其指向空。
实现:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* removeElements(struct ListNode* head, int val)
{
struct ListNode* cur=head;
struct ListNode* newhead=NULL,*tail=NULL;
while(cur)
{
//删除
if(cur->val==val)
{
struct ListNode* del=cur;
cur=cur->next;
free(del);
}
else
{
//尾插(头插)
if(tail==NULL)
{
//头节点等于尾节点
newhead=tail=cur;
}
else
//正常尾插
{
//尾节点指向插入的节点
tail->next=cur;
//尾节点向后移动
tail=tail->next;
}
//遍历节点后移
cur=cur->next;
}
}
//遍历完成后,将尾节点指向空
if(tail)
{
tail->next=NULL;
}
return newhead;
}
法1:先遍历一遍找出链表的长度,再遍历一遍找出中间节点。
但如果要求只能遍历一遍呢?
法2:这时就可以定义两个指针,一个走得慢,另一个走得快,速度是慢指针的两倍,
在链表有偶数个节点的情况下,快指针走到尾节点(下一个节点为空)的时候,慢指针刚好走到链表中间节点。
在链表为奇数个节点的情况下,当快指针走到尾节点后面一个位置(空)的时候,慢指针恰好处于链表的第二个中间节点。
无论是奇数个还是偶数个节点,直接返回慢节点,就能得到我们需要的结果。
实现:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* middleNode(struct ListNode* head){
struct ListNode* slow=head,*fast=head;
while(fast&&fast->next)
{
slow=slow->next;
fast=fast->next->next;
}
return slow;
}
链表中倒数第k个结点_牛客题霸_牛客网 (nowcoder.com)
本题同样采用和上题类似的快慢指针的做法,使用了 相对距离 的原理。
先让快指针向前移动 k 个节点,这样快慢指针之间的距离就为 k 个节点;
我们的目的是找出倒数第 k 个节点,当快指针走到 NULL 的时候,慢指针就刚好走到倒数第 k 个节点的位置上了。
我们还需要考虑到如果链表为空或者 k <=0的情况,直接返回NULL;如果 k 大于链表的长度,也要返回空。
实现:
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
/**
*
* @param pListHead ListNode类
* @param k int整型
* @return ListNode类
*/
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
if(!pListHead||k<=0)
{
return NULL;
}
struct ListNode* slow=pListHead,*fast=pListHead;
while(k--)
{
if(fast)
{
fast=fast->next;
}
else {
return NULL;
}
}
while(fast)
{
slow=slow->next;
fast=fast->next;
}
return slow;
}
创建一个新的空链表,并记录它的头节点和尾节点;
比较两个链表相同位置节点的大小,较小的一方就头插到新链表中,直到有两个链表中的其中一个先走到了空,结束插入,将有剩余的链表链接到新链表。
需要注意如果开始就有一条链表为空,那么直接返回另外一条链表。
实现:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
if(list1==NULL)
{
return list2;
}
if(list2==NULL)
{
return list1;
}
struct ListNode* phead=NULL,*tail=NULL;
while(list1&&list2)
{
if(list1->val<list2->val)
{
if(tail==NULL)
{
phead=tail=list1;
}
else
{
tail->next=list1;
tail=tail->next;
}
list1=list1->next;
}
else
{
if(tail==NULL)
{
phead=tail=list2;
}
else
{
tail->next=list2;
tail=tail->next;
}
list2=list2->next;
}
}
if(list1==NULL)
{
tail->next=list2;
}
if(list2==NULL)
{
tail->next=list1;
}
return phead;
}