链表OJ总结

1.移除链表元素

思路1:

直接在原链表中改动,创建两个标记指针一个为当前指针(cur)另一个为前指针(pre),将head的值赋值给cur。

遍历链表。

当链表中的值为要求移除的值。

先判断pre是否为空,因为头指针要移除的方式和别的不同,将head的next赋值给cur,将头指针free掉,再将cur的值赋值给head。

如果pre不为空,且cur的值又等于要删除的值时,将前指针pre的next指向cur的下一个指针,将当前指针释放,再将前指针的next赋值给cur。

如果cur的值不等于要求删除的值则将当前指针cur赋值给前指针pre,再将cur的next赋值给cur。

代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */

//直接在原链表中改动
struct ListNode* removeElements(struct ListNode* head, int val){
    if(head==NULL)
    {
        return NULL;
    }
    struct ListNode* pre=NULL;
    struct ListNode* cur=head;
    while(cur)
    {
        if(cur->val==val)
        {
            if(pre)
            {
                pre->next=cur->next;
                free(cur);
                cur=pre->next;
            }
            else
            {
                cur=head->next;
                free(head);
                head=cur;
            }
        }
        else
        {
            pre=cur;
            cur=cur->next;
        }
    }
    return head;
}

思路2:

创建一个哨兵位相较于思路1少了一步判断前指针是否为空。创建一个哨兵位,并将其赋值给两个新建标记指针newhead和tail,再创建一个当前指针标记cur,用于遍历链表。

当cur的值不等于要求删除的值时,将尾指针指tail指向当前指针cur即tail->next=cur,再将尾指针tial移至tail的下一个节点即tail=tail->next。同时将cur移至链表下一个节点。

如果cur的值等于要删除的值时,则创建一个要删除的指针标记del,先将cur移至下一个节点再将del释放,顺序不能乱,如果先释放del就找不到cur的下一个节点了。

最后当遍历完成后,将tail的next置空代表新链表结束,并将newhead的next赋值给head,将newhead的空间释放并将head返回即可。

代码如下:

struct ListNode* removeElements(struct ListNode* head, int val){
    struct ListNode* newhead=(struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* tail=newhead;
    struct ListNode* cur=head;
    while(cur)
    {
        if(cur->val!=val)
        {
            tail->next=cur;
            tail=tail->next;
            cur=cur->next;
        }
        else
        {
            struct ListNode* del=cur;
            cur=cur->next;
            free(del);
        }
    }
    tail->next=NULL;
    head=newhead->next;
    free(newhead);
    return head;
}

2.反转链表

思路:将原链表的所有节点的指向逆置,创建三个标记指针,前指针pre,当前指针cur,下一个指针next,遍历原链表,将前一个指针pre赋值给当前指针cur的next,如此循环直至遍历结束。需要注意的是,当前指针cur会移动到空指针区域,所以最后返回的指针是前指针pre。

代码如下:

struct ListNode* reverseList(struct ListNode* head){
    if(head==NULL)
    {
        return NULL;
    }
    struct ListNode* pre=NULL;
    struct ListNode* cur=head;
    struct ListNode* next=head->next;
    while(cur)
    {
        cur->next=pre;
        pre=cur;
        cur=next;
        if(next)
        {
            next=next->next;
        }
    }
    return pre;
}

3.链表中间节点

思路:创建两个标记指针,一个快指针一个慢指针,慢指针一次走一步,快指针一次走两步。当fast或者fast的next其中一个为空时,slow刚好走到中间节点处。

循环条件为什么是fast&&fast->next不为空呢?

因为当节点个数为奇数时,如果条件只是fast不为空的话,fast走到最后一个节点满足条件,再走两步会出现NULL->next的问题。

代码如下:

struct ListNode* middleNode(struct ListNode* head){
    struct ListNode* slow=head;
    struct ListNode* fast=head;
    while(fast&&fast->next)
    {
        slow=slow->next;
        fast=fast->next->next;
    }
    return slow;
}

4.链表中倒数第k个结点

思路:创建两个标记指针,一个slow指针,一个fast指针。

要求你输出倒数第k个节点时,fast先走k步,然后再进入循环,slow和fast都是每次走一步,当fast为空时则结束循环,此时的slow刚好是倒数第k个节点。

需要注意的是,可能k的值会超出链表长度或者k为0的情况。

先分析k超出链表长度的情况,在实现fast先走k步时就可以加入判断,如果fast为空时,还进入了循环,那么可以判断k的值超出链表长度,返回NULL即可。

第二种特殊情况,k为0时,则fast先走0步,循环结束后slow和fast都为NULL,则其返回的slow为NULL。

代码如下:

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
    // write code here
    struct ListNode* slow=pListHead;
    struct ListNode* fast=pListHead;
    for(int i=0;i<k;i++)
    {
        if(fast==NULL)
        {
            return NULL;
        }
        fast=fast->next;
    }
    while(fast)
    {
        slow=slow->next;
        fast=fast->next;
    }
    return slow;
}

5.合并两个有序链表

思路:

首先可能存在三种特殊情况可以直接返回,根据代码可以看出。

非特殊情况时,创建哨兵位newhead和标记指针tail,两个初始化的值均为哨兵位的地址,在创建一个标记指针head置空。这里的主要思想是判断两个链表当前节点的大小进行尾插。

循环条件为当list1或者list2其中一个链表结束即其中一个为空时结束循环,需要注意的是循环的条件是当其中一个链表遍历完时就结束了,不能保证另一个链表也遍历完了,所以需要在最后判断一下,如果不为空则直接将未结束的链表接在tail->next处。

代码如下:

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
    if(list1==NULL&&list2!=NULL)
    {
        return list2;
    }
    if(list1!=NULL&&list2==NULL)
    {
        return list1;
    }
    if(list1==NULL&&list2==NULL)
    {
        return NULL;
    }
    struct ListNode* newhead=(struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode* tail=newhead;
    struct ListNode* 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!=NULL)
    {
        tail->next=list1;
    }
    if(list2!=NULL)
    {
        tail->next=list2;
    }
    head=newhead->next;
    free(newhead);
    return head;
}

6.链表分割

思路:创建两个哨兵位,一个哨兵位是小于x的节点尾插至lesslist链表中,另一个哨兵位是大于x的节点尾插至greaterlist链表中,再将lesstial的next与greaterhead的next连接。需要注意的是要将greatertail的next置空,如果不置空会出现环状链表。

代码如下:

ListNode* partition(ListNode* pHead, int x) {
        // write code here
        if(pHead==NULL)
        {
            return NULL;
        }
        struct ListNode* lesslist=(struct ListNode*)malloc(sizeof(struct ListNode));
        struct ListNode* greaterlist=(struct ListNode*)malloc(sizeof(struct ListNode));
        struct ListNode* lesshead,*lesstail,*greaterhead,*greatertail;
        lesshead=lesstail=lesslist;
        greaterhead=greatertail=greaterlist;
        struct ListNode* cur=pHead;
        while(cur)
        {
            if(cur->val<x)
            {
                lesstail->next=cur;
                lesstail=lesstail->next;
            }
            else
            {
                greatertail->next=cur;
                greatertail=greatertail->next;
            }
            cur=cur->next;
        }
        lesstail->next=greaterhead->next;
        greatertail->next=NULL;

        pHead=lesshead->next;
        free(lesshead);
        free(greaterhead);
        return pHead;
    }

7.链表的回文结构

思路:

将中间节点之前的节点指向都逆置,根据链表节点的个数是奇数还是偶数,向两个方向遍历并判断值是否相等和结束标志是否一致。

设置标记指针slow,fast,前指针pre,当前指针cur,下一个指针next。

快指针一次走两步,慢指针一次走一步,先将中间节点之前的节点指向逆置。

判断fast是否为空指针,如果是空指针那么链表的节点个数为偶数个,先将当前指针cur的next改回指向next,然后判断pre和cur指向的值是否相等,如果不相等则返回false。因为循环条件是其中一个标记为空就结束,并不知道另一个节点是否为空,所以在循环结束后要判断一下两个指针是否都为空,如果有一个不为空就返回false。

如果fast部位空指针,那么链表节点个数为奇数个,那么判断的标记指针与节点个数为偶数个的标记指针不同,判断的是pre和next的值是否相等,注意的点同上。

当都满足条件时则返回true。

代码如下:

bool chkPalindrome(ListNode* A) {
        if(A==NULL)
        {
            return false;
        }
        // write code here
        struct ListNode* slow=A;
        struct ListNode* fast=A;
        struct ListNode* pre=NULL;
        struct ListNode* cur=A;
        struct ListNode* next=A->next;
        while(fast&&fast->next)
        {
            slow=slow->next;
            fast=fast->next->next;
            cur->next=pre;
            pre=cur;
            cur=next;
            next=next->next;
        }
        if(fast==NULL)
        {
            cur->next=next;
            while(pre&&cur)
            {
                if(pre->val!=cur->val)
                {
                    return false;
                }
                pre=pre->next;
                cur=cur->next;
            }
            if(pre!=NULL||cur!=NULL)
            {
                return false;
            }
        }
        else 
        {
            while(pre&&next)
            {
                if(pre->val!=next->val)
                {
                    return false;
                }
                next=next->next;
                pre=pre->next;
            }
            if(pre!=NULL||next!=NULL)
            {
                return false;
            }
        }
        return true;
    }

8.相交链表

思路:计算两个链表的长度,然后计算他们的差,让长的链表先走他们的差的步数,再一起走,当两个链表的当前节点指针相等时,返回任意其中一个节点的地址。

代码如下:

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    struct ListNode* cur1=headA;
    struct ListNode* cur2=headB;
    int lena=1;
    int lenb=1;
    while(cur1)
    {
        lena++;
        cur1=cur1->next;
    }
    while(cur2)
    {
        lenb++;
        cur2=cur2->next;
    }
    int len=lena>lenb?lena-lenb:lenb-lena;
    if(lena>lenb)
    {
        for(int i=0;i<len;i++)
        {
            headA=headA->next;
        }
    }
    else
    {
        for(int i=0;i<len;i++)
        {
            headB=headB->next;
        }
    }
    while(headA!=headB)
    {
        headA=headA->next;
        headB=headB->next;
    }
    return headA;
}

9.环形链表

思路:

创建两个标记指针,slow和fast,slow一次走一步,fast一次走两步。

循环当fast或者fast->next为空时,则说明该链表不是环形链表。

如果不为空时,则继续循环,当fast和slow再次相等的话那么该链表为环形链表。

代码如下:

bool hasCycle(struct ListNode *head) {
    struct ListNode* slow=head;
    struct ListNode* fast=head;
    while(fast&&fast->next)
    {
        slow=slow->next;
        fast=fast->next->next;
        if(slow==fast)
        {
            return true;
        }
    }
    return false;
}

10.返回环形链表入环的第一个节点

思路1:

创建两个标记指针,分别为快慢指针,慢指针一次走一步,快指针一次走两步,先找到相遇点。

然后一个从头节点开始走,一个从相遇点开始走,当head等于meet时则该点为环的入口节点。

为什么?

根据图进行分析

假设链表头到环的入口点的距离为:L

环入口点到相遇点的距离为:X

环的长度为:C

slow走的路程为:L+X

fast走的路程为:L+n*C+X

为什么时n*C?因为可能在slow进环前,fast已经绕了很多了圈。

2*(L+X)=L+n*C+X

L=n*C-X

由此得出一个从相遇点开始走,另一个从头节点开始走,相遇时就是环的入口点。

代码如下:

struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode* slow=head;
    struct ListNode* fast=head;
    struct ListNode* meet=NULL;
    while(fast&&fast->next)
    {
        slow=slow->next;
        fast=fast->next->next;
        if(slow==fast)
        {
            meet=fast;
            while(meet!=head)
            {
                head=head->next;
                meet=meet->next;
            }
            return meet;
        }
    }
    return NULL;
}

11.总结以上题型思路

在移除链表元素中用到尾插的思想;

在反转链表时可以通过逆置各个结点指向或者时利用头插法实现;

求链表中间节点可以通过快慢指针的方式去解决;

倒数第k个节点则是让快指针先走k步然后再一起走的方法去求得结果;

合并两个有序链表也是通过尾插思路解决;

链表分割也是采用了尾插思想解决问题;

链表得回文结构则是通过快慢指针先逆置中间节点之前得结点指向,然后从中间开始往两个方向遍历判断是否是回文结构;

相交链表先求出两个链表的长度,让长的链表先走两链表长度之差的步数,再一起走,当走到同一个节点时则返回该节点;

判断是否为环形链表利用快慢指针解决;

求出环形链表的入口点有两种思路:第一种思路是先判断是否为环形链表,如果是就找到相遇点,分别设置两个标记指针一个从相遇点开始走,另一个从头开始走,当他们走到同一个节点时则返回该节点;

第二种思路是将相遇点的下一个节点作为一个新的头节点,相遇点作为一个新的头节点,变成相交链表问题解决。

综上所述:链表基础增加节点的方法头插法尾插法可以先尝试思考能否解决问题。除此之外创建哨兵位可以不用为处理头节点问题做特殊处理。快慢指针的思路也是一种常见的解题方式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值