链表经典题

题目速览

题目一
在这里插入图片描述
题目二
在这里插入图片描述
题目三
在这里插入图片描述
题目四
在这里插入图片描述
题目五
在这里插入图片描述
题目六
在这里插入图片描述
题目七
在这里插入图片描述
题目八
在这里插入图片描述
题目九
在这里插入图片描述
题目十
在这里插入图片描述
题目十一
在这里插入图片描述
题目十二
在这里插入图片描述


一:删除结点中给定值的所有结点

在这里插入图片描述
法一:构建头结点法
此题所给出的是一个没有头结点的链表,没有头结点的链表在操作时对于第一个结点的处理非常苦难,要考虑很多种情况,所以我们可以自己造一个头结点,最终返回的时候再head放到该放到的位置上。这种方法其实有点作弊的嫌疑,但是无疑是一种非常好的方法
在这里插入图片描述

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


struct ListNode* removeElements(struct ListNode* head, int val)
{
     
     
    struct ListNode* dummy=(struct ListNode*)malloc(sizeof(struct ListNode));//制造头结点
    dummy->next=head;//头结点下一个就是第一个元素
    dummy->val=NULL;//头结点不含数据元素
    struct ListNode* pre=dummy;
    struct ListNode* cur=head;//双指针法
    while(cur!=NULL){
     
     
        if(cur->val!=val)
        {
     
     
            cur=cur->next;
            pre=pre->next;
        }else
        {
     
     
            pre->next=cur->next;
            cur=cur->next;
        }
    }
    head=dummy->next;//一定要把头指针找回
    free(dummy);
    return head;
}



法二:迭代法
这种方法要注意一点,由于pre指针在初始状态下是空,cur指针首先指向head,所以如果第一个结点就是要删除的结点,那么进行删除操作时“pre->next=cur->next”的操作显然就是有问题的,所以说对于这种情况要特殊处理

struct ListNode* removeElements(struct ListNode* head, int val)
{
     
     
    struct ListNode* pre=NULL;
    struct ListNode* cur=head;
    while(cur!=NULL)//cur进行扫描
    {
     
     
        if(head->val==val)//特殊情况,第一个结点就是要删除的结点
        {
     
     
            head=cur->next;//head向后走
            free(cur);
            cur=head;//一定注意这一点,因为没有这句cur就成了野指针了
        }
        else//一般情况
        {
     
     
            if(cur->val==val)//所指结点就是要删除的结点
            {
     
     
                pre->next=cur->next;
                free(cur);
                cur=pre;//一定注意这一点,因为没有这句cur就成了野指针了
            }
            else//不是就迭代
            {
     
     
                pre=cur;
                cur=cur->next;
            }
        }
    }
    return head;
}

在这里插入图片描述


二:反转单链表

在这里插入图片描述
法一:迭代法
使用迭代的方法逆置链表,需要用到三个指针
在这里插入图片描述
在这里插入图片描述

struct ListNode* reverseList(struct ListNode* head) 
{
     
     
    //递归
    /*if (head == NULL || head->next == NULL) 
    {
        return head;
    }
    struct ListNode* newHead = reverseList(head->next);
    head->next->next = head;
    head->next = NULL;
    return newHead;
    */
    struct ListNode* pre=head;
    struct ListNode* cur=NULL;
    struct ListNode* save;
    while(pre!=NULL)
    {
     
     
        save=pre;
        pre=pre->next;
        save->next=cur;
        cur=save;
    }

    return cur;
}

法二:头插法
把原来的结点取下来,然后进行头插
在这里插入图片描述

struct ListNode* newHead=NULL;
    struct ListNode* cur=head;
    while(cur != NULL)
    {
     
     
        head=cur;
        cur=cur->next;
        head->next=newHead;
        newHead=head;
    }
    return newHead;

在这里插入图片描述


三:取链表的中间结点

在这里插入图片描述
法一:普通解法
这种方法是最容易想到的方法,首先遍历链表,得到总的结点数,然后找出中间节点,再次遍历即可。

 int count=0;
    struct ListNode* cur=head;
    while(cur!=NULL)
    {
     
     
        count++;
        cur=cur->next;
    }
    int ret=count/2;
    cur=head;
    while(ret)
    {
     
     
        cur=cur->next;
        ret--;
    }
    return cur;

法二:双指针法
一般来说,遍历一次或者稍加巧妙的方法,就可以用到双指针。定义一个慢指针和快指针,快指针每次走两步,慢指针每次走一步,最后慢指针所指就是中间节点
在这里插入图片描述

 struct ListNode* slow=head;
    struct ListNode* fast=head;
    while(fast->next!=NULL)
    {
     
     
        fast=fast->next->next;
        slow=slow->next;
        if(fast==NULL)//这一点注意:对于偶数个结点,最后一次fast为NULL,如果不跳出,继续判断的话会出错
            break;
    }
    return slow;

在这里插入图片描述


四:取链表倒数第K个结点

在这里插入图片描述
此题可以用朴素解法,也可以将链表导致,求其第k个结点。但更为好的方法还是双指针法
在这里插入图片描述

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) 
{
     
     
    struct ListNode* slow=pListHead;
    struct ListNode* fast=pListHead;
    while(k--)
    {
     
     
       if(fast)//注意有一种非常特殊的情况就是,链表有5个结点但是让你求倒数第8个结点,这种情况会出现内存错误
           fast=fast->next;
        else
            return NULL;
    }
    
    while(fast!=NULL)
    {
     
     
        
        slow=slow->next;
        fast=fast->next;
        
    }
    return slow;
}

在这里插入图片描述


五:合并链表

在这里插入图片描述
思路不难,创建新的链表,然后分别遍历两个原链表,把小的插入

struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2)
{
     
     
    struct ListNode* cur1=l1;
    struct ListNode* cur2=l2;
    struct ListNode* l3=(struct ListNode*)malloc(sizeof(struct ListNode));
    l3=NULL;
    struct ListNode* cur3=l3;
    if(cur1==NULL && cur2!=NULL)//如果第一个空,第二个不空
    {
     
     
        return cur2;
    }
     if(cur1!=NULL && cur2==NULL)//如果第二个空,第一个不空
    {
     
     
        return cur1;
    }

    while(cur1!=NULL && cur2!=NULL)
    {
     
     
        if(cur2->val<cur1->val)
        {
     
     
            if(l3==NULL)//特殊处理头结点
            {
     
     
                l3=cur2;
                cur3=l3;
                cur2=cur2->next;
            }
            else
            {
     
     
                cur3->next=cur2;
                cur2=cur2->next;
                cur3=cur3->next;
            }
        }
        else
        {
     
     
            if(l3==NULL)//特殊处理头结点
            {
     
     
                l3=cur1;
                cur3=l3;
                cur1=cur1->next;
            }
            else
            {
     
     
                cur3->next=cur1;
                cur1=cur1->next;
                cur3=cur3->next;
            }
        }
    }
    if(cur3==NULL)//l1和l2同时是空链表
    {
     
     
        return cur3;
    }
    if(cur1==NULL)//cur1等于NULL了说明,说明l2长于l1
    {
     
     
        cur3->next=cur2;
    }
    if(cur2==NULL)//cur2等于NULL了说明,说明l1长于l2
    {
     
     
        cur3->next=cur1;
    }
    return l3;
}

在这里插入图片描述


六:分割链表

在这里插入图片描述
此题要注意,要保持相对顺序不变,比如4->3->2->1,要把所有小于3的结点放在其余结点之前,那么可以是2->1->4->3,但是决不能是1->2->4->3或2->1->3->4等。

这个题类似于上述合并链表,选定题目要求的元素,遍历此链表,小于此元素的尾插到一个链表,大于的则尾插到另一个链表。但是尾插时会有一个问题,就是新创造的头指针开始为NULL,尾插时会出现NULL错误,所以为了避免这种问题,我们可以创造一个“哨兵”结点,该哨兵结点并不存储任何有效数据,但是它能使尾插的代码统一变为"head->next==Newhead"。
在这里插入图片描述

class Partition {
     
     
public:
    ListNode* partition(ListNode* pHead, int x) 
    {
     
     

        struct ListNode*small_tail=NULL;//小的尾节点
        struct ListNode*small_guard=(ListNode*)malloc(sizeof(ListNode));//小的哨兵结点
        small_guard->next=NULL;
        small_tail=small_guard;
        
        struct ListNode*large_tail=NULL;//大的尾节点
        struct ListNode*large_guard=(ListNode*)malloc(sizeof(ListNode));//大的哨兵结点
        large_guard->next=NULL;
        large_tail=large_guard;
        
        struct ListNode* cur=pHead;//遍历指针
        while(cur)
        {
     
     
            if(cur->val<x)//小于x进行samll链表的尾插
            {
     
     
                small_tail->next=cur;
                small_tail=small_tail->next;
                cur=cur->next;  
            }
            else//大于x进行large链表的尾插
            {
     
     
                large_tail->next=cur;
                large_tail=large_tail->next;
                cur=cur->next;
            }
        }
        small_tail->next=large_guard->next;//小的后面连上的大的
        large_tail->next=NULL;//注意收尾
        
        return small_guard->next;
        
    }
};

在这里插入图片描述


七:链表的回文结构

在这里插入图片描述
所谓回文数,就是指正读和反读一样的数字,比如1221。
题目中对于空间复杂度和时间复杂度都有限制,所以就不能使用建立数组的方式进行比较,即使没有这种限制,采用此方法也是不可取的,因为一旦链表过长,时间复杂度将会很大。
我们的思路是,利用之前讲过的双指针法找到中间节点,以中间节点为新的链表,对后半部分进行逆置操作,然后进行比较
这里有几点需要说明
在这里插入图片描述

ListNode* reverse(ListNode* head)//逆置
{
     
     
    ListNode* newHead=NULL;
    ListNode* cur=head;
    while(cur)
    {
     
     
        head=cur;
        cur=cur->next;
        head->next=newHead;
        newHead=head;
    }
    return newHead;
}

class PalindromeList{
     
     
public:
    bool chkPalindrome(ListNode* A){
     
     
        ListNode* slow=A;
        ListNode* fast=A;
        ListNode* pre=NULL;
        while(fast->next)
        {
     
     
            pre=slow;
            fast=fast->next->next;
            slow=slow->next;
            if(fast==NULL)
            {
     
     
                break;
            }
        }
        pre->next=NULL;//断开
        slow=reverse(slow);//逆置,此时slow指向“原链表最后一个”
        while(A)//夹逼比较
        {
     
     
            if(A->val!=slow->val)//一旦出现不相等,一定不相等
            {
     
     
               return false;
            }
            else
            {
     
     
                A=A->next;
                slow=slow->next;
            }
        }    
        return true;//要是可以执行到这里,那么肯定是回文结构
 }
};

在这里插入图片描述


八:相交链表

在这里插入图片描述
此题最大的障碍就是,两个链表的长度可能不一致,这样一来就无法比较了。所以可以先分别求出两个链表的长度,接着计算出他们的差距,让长链表先走完这段差距,这样一样短的和长的就能同时开始向后走了,一旦出现某个位置相同,那么此位置必然是相交位置

注意下面对于长短链表的处理,如果硬要判断出A长还是B长,那么就要进行分类。所以可以让一个变量longlist始终保存最长的那个链表的地址(也就是先假设再调整)。

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
     
     
    int length_A=0;
    int length_B=0;
    int gap=0;
    struct ListNode* curA=headA;
    struct ListNode* curB=headB;
    while(curA)
    {
     
     
        ++length_A;
        curA=curA->next;
    }
    while(curB)
    {
     
     
        ++length_B;
        curB=curB->next;
    }

    struct ListNode* longlist=headA;//注意以下处理
    struct ListNode* shortlist=headB;
    if(length_B>length_A)
    {
     
     
        longlist=headB;
        shortlist=headA;
    }
    gap=abs(length_A-length_B);
    while(gap--)//先让长的走完差距
    {
     
     
        longlist=longlist->next;

    }
    while(longlist)
    {
     
     
        if(longlist==shortlist)
            return longlist;
        longlist=longlist->next;
        shortlist=shortlist->next;
    }
    return NULL;
}

在这里插入图片描述

九:环状链表

在这里插入图片描述
可以用快慢指针,让快指针先走(每次2步),慢指针后走(每次1步),当两个指针都进入环内时,经过一定次数,一定有快慢指针相等,也就证明有环

也就是此题逻辑为,如果fast某一刻为NULL了,则一定没有环,但是如果有环,那么fast和slow一定能相遇,从而判断出来

这里还需要特别注意,快指针必须每次2步,因为如果其他步数,就会存在覆盖不全的情况,或者说相遇的几率大大减小,有可能仅仅只差一步,但是fast恰好越过slow,类似于死循环了。
这一点可以证明一下它们一定相遇
在这里插入图片描述

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

在这里插入图片描述

十:环状链表2

在这里插入图片描述
思路一
在这里插入图片描述

struct ListNode *detectCycle(struct ListNode *head) 
{
     
     
    if(head == NULL)//如果链表为空,直接返回空
        return NULL;
    struct ListNode* slow=head;
    struct ListNode* fast=head;
    struct ListNode* cur1=head;
    struct ListNode* cur2=NULL;//cur1和cur2分别指向断开后的两个链表的头部
    int lengthA=0;
    int lengthB=0;
    int gap=0;
    while(fast && fast->next)
    {
     
     
        slow=slow->next;
        fast=fast->next->next;
        if(fast==slow)
        {
     
     
            break;//链表有环
        }
    }
    if(fast==NULL)//做安全处理,防止fast=NULL,而执行fast=fast->NULL
        return NULL;
    fast=fast->next;//断开操作
    slow->next=NULL;
    cur2=fast;
    while(cur1)//下面全是相交链表的代码
    {
     
     
        ++lengthA;
        cur1=cur1->next;
    }
    while(cur2)
    {
     
     
        ++lengthB;
        cur2=cur2->next;
    }
    struct ListNode* longlist=head;
    struct ListNode* shortlist=fast;
    if(lengthB>lengthA)
    {
     
     
        longlist=fast;
        shortlist=head;
    }
    gap=abs(lengthA-lengthB);
    while(gap--)
    {
     
     
        longlist=longlist->next;
    }
    while(longlist)
    {
     
     
        if(longlist==shortlist)
            return longlist;
        longlist=longlist->next;
        shortlist=shortlist->next;
    }
    return NULL;

}

在这里插入图片描述
思路二
这种思路,需要进行证明,且不太好理解,但是代码十分简单
在这里插入图片描述

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

十一:复杂链表复制

在这里插入图片描述
在这里插入图片描述

struct Node* copyRandomList(struct Node* head)
{
     
     
    if(head==NULL)
    {
     
     
        return NULL;
    }
    struct Node* cur=head;
    while(cur)//复制链表,复制一个就把它接到原来的后面,相当于新的链表和原来的链表错一位
    {
     
     
        struct Node* NewNode=(struct Node*)malloc(sizeof(struct Node));
        NewNode->next=NULL;
        NewNode->random=NULL;
        NewNode->val=cur->val;

        struct Node* next=cur->next;
        cur->next=NewNode;
        NewNode->next=next;
        cur=next;
    }

    cur=head;
    while(cur)//新链表结点的random等于,对应旧链表的结点random的下一个
    {
     
     
        struct Node* behind=cur->next;
        if(cur->random!=NULL)
            behind->random=cur->random->next;
        else
            behind->random=NULL;
        cur=cur->next->next;
    }


    cur=head;
    struct Node* returnhead=head->next;
    while(cur)//把链表断开
    {
     
     
        struct Node* NewNode=cur->next;
        struct Node* next=NewNode->next;
        cur->next=next;
        if(next==NULL)
        {
     
     
            NewNode->next=NULL;
            break;
        }
        NewNode->next=next->next;

        cur=next;
    }
    return returnhead;
 
}

在这里插入图片描述

十二:链表插入排序

将一个单链表进行插入排序
在这里插入图片描述
此题,可以使用一个sorthead保存头结点,然后剩余结点挨个插入,具体插入时注意以下细节
在这里插入图片描述

struct ListNode* insertionSortList(struct ListNode* head)
{
     
     
    if(head==NULL)//如果传回来的是空链表,直接返回
    {
     
     
        return NULL;
    }
    
    struct ListNode*next=NULL;
    struct ListNode* cur=NULL;
    struct ListNode* sorthead=head;//使用sorthead保存head
    head=head->next;从head的下一个结点开始逐个插入
    sorthead->next=NULL;//注意这一点不要忘了,不然会陷入死循环
    while(head)//head一直往后走,作用时检视每个待插入结点
    {
     
     
        if(head->val<sorthead->val)//第一种情况就是如果待插入的结点小于头结点,那么进行头插
        {
     
     
            next=head->next;
            head->next=sorthead;
            sorthead=head;//注意把头更新
            head=next;
        }
        else//第二种情况是大于等于,此时就要在链表内进行逐个比较插入
        {
     
     
            cur=sorthead;//cur用于扫描sorthead这个链表
            while(cur)
            {
     
     
                if(cur->next==NULL)//一个特殊情况——一旦扫描到最后一个结点,那么说明此时待插入的结点相较于sorthead中的所有结点来说是最大的,所有插入末尾即可
                {
     
     
                    next=head->next;
                    cur->next=head;
                    cur=cur->next;
                    head=next;
                    cur->next=NULL;
                    break;//一旦插入就跳出循环,不然会死循环
                }
                if(head->val<(cur->next)->val)//这里必须使用(cur->next)->val,不能用cur->val,因为在插入时我们要插入到前面,而单链表只可以找到后面
                {
     
     
                    next=head->next;
                    head->next=cur->next;
                    cur->next=head;
                    head=next;
                    break;//注意跳出
                }
                else
                {
     
     
                    cur=cur->next;//如果不符合上述两种情况,那么cur向后走,继续比较
                }
            }
        }
    }
    return sorthead;
}

在这里插入图片描述

十三:链表移除全部重复元素

在这里插入图片描述
此题思路不难,但是需要考虑的情况较多,容易写错
首先是正常情况1->2->3->3->4->4->5
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
链表由于其特殊性,所以出错的地方往往就是头或尾,而本题没有通过全部用例,也正是因为头尾这个特殊情况需要特殊处理
特殊情况一1->1->1->3->4
在这里插入图片描述
在这里插入图片描述
特殊情况2:1-2-3-3-3
在这里插入图片描述
在这里插入图片描述
最后:
在这里插入图片描述

class Solution {
     
     
public:
    ListNode* deleteDuplication(ListNode* pHead)
    {
     
     
       if(pHead==NULL || pHead->next==NULL)
       {
     
     
           return pHead;
       }
        ListNode* prev=NULL;
        ListNode* cur=pHead;
        ListNode* next=cur->next;
        
        while(next)
        {
     
     
            if( cur->val != next->val)
            {
     
     
                prev=cur;
                cur=next;
                next=next->next;
            }
            else
            {
     
     
                while(next && cur->val==next->val)
                {
     
     
                    next=next->next;
                }
                if(prev!=NULL)
                {
     
     
                    prev->next=next;
                }
                else
                {
     
     
                    pHead=next;
                }
               
                cur=next;
                if(next)
                    next=cur->next;
            }
        }
        return pHead;
      
    }
};

猜你喜欢

转载自blog.csdn.net/qq_39183034/article/details/112686504