LeetCode和牛客网经典链表题目合集

目录

LeetCode第203题:移除链表元素

LeetCode第206题:反转链表

LeetCode第876题:链表的中点节点 

牛客网:链表中倒数最后k个节点

LeetCode第21题:合并两个有序链表

牛客网:分割链表

牛客网:链表的回文结构

LeetCode第160题:相交链表

LeetCode第141题:环形链表

LeetCode第142题:环形链表II

LeetCode第138题:复制带随机指针的链表 


本期博客我将对一些经典的链表题进行总结详解

LeetCode第203题:移除链表元素

题目链接:

203. 移除链表元素

对于此题我的想法是直接将链表中等于val值的节点从链表中移除就行:

struct ListNode* removeElements(struct ListNode* head, int val) 
{
    struct ListNode* temp1 = head;
    struct ListNode* temp2 = head;
    int i = 0;
    if (temp1 == NULL)
    {
        return head;
    }
    while (temp1)
    {
        if (i == 0)
        {
            if (head->val == val)
            {
                head = head->next;
                temp1 = head;
                temp2 = head;
            }
            else
                i++;
        }
        else
        {
            if (temp1->val == val)
                temp2->next = temp1->next;
            else
                temp2 = temp1;
            temp1 = temp1->next;
        }
    }
    return head;
}

成功通过:

LeetCode和牛客网经典链表题目合集_第1张图片

LeetCode第206题:反转链表

题目链接:

 206. 反转链表

本题我先想到了使用递归的方法,先用函数层层递归找到链表最后一个节点,再将每一层所传的节点用尾插法存入一个全局结构体指针变量temp中,最后返回temp这个指针就可以了:

void SListPushBack(struct ListNode** pplist, struct ListNode* node)//这里由于我们要改变链表指针的指向,需要使用二级指针来控制
{
    struct ListNode* newplist = node;//创建一个新节点为插入链表做准备
    newplist->next=NULL;
    if (*pplist == NULL)//如果传入的是个空链表就直接插入一个新节点
    {
        *pplist = newplist;
        return;
    }
    //传入的不是空链表就找到链表尾部
    struct ListNode* tail = *pplist;
    while (tail->next)
    {
        tail = tail->next;
    }
    tail->next = newplist;//在链表尾部插入数据
}
struct ListNode* temp = NULL;
void returnNode(struct ListNode* node)
{
    if (node == NULL)
        return;
    returnNode(node->next);
    SListPushBack(&temp, node);
}
struct ListNode* reverseList(struct ListNode* head) {
    temp = NULL;
    returnNode(head);
    return temp;
}

使用递归的方法时间复杂度为O(n),空间复杂度为O(n)运行起来不是很理想:

LeetCode和牛客网经典链表题目合集_第2张图片

接下来使用迭代法来解决它:

在遍历链表时,将当前节点的 next 指针改为指向前一个节点。由于节点没有引用其前一个节点,因此必须事先存储其前一个节点。在更改引用之前,还需要存储后一个节点。最后返回新的头引用。

struct ListNode* reverseList(struct ListNode* head) {
    if (head == NULL || head->next == NULL)
        return head;
    struct ListNode* temp = NULL, * new = NULL, * List = head->next;
    int i = 0;
    while (List->next)
    {
        if (i == 0)
        {
            new = head;
            new->next = NULL;
            i++;
        }
        else {
            temp = List;
            List = List->next;//在temp被修改之前要提前找到链表的下一个节点,否则在下一步temp->被修改时List也会被修改从而丢失链表的下一个节点
            temp->next = new;
            new = temp;
        }
    }
    return new;
}

此方法只需要遍历一次链表时间复杂度为O(n),空间复杂度为O(1)的效果:

LeetCode和牛客网经典链表题目合集_第3张图片

LeetCode第876题:链表的中点节点 

题目链接:

 876. 链表的中间结点

此题我们使用快慢指针的方法可以快速的解决掉该题:

设置两个遍历链表的指针fast和slow指针,fast指针每次遍历链表的两个节点,slow每次遍历链表的一个节点。当fast指针遍历完链表之后slow指针才遍历完整个链表的一半。此时返回slow指针即为链表的中间节点。

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

此方法只要遍历一次链表时间复杂度为O(n),空间复杂度为O(1):

LeetCode和牛客网经典链表题目合集_第4张图片

牛客网:链表中倒数最后k个节点

题目地址:   

链表中倒数最后k个结点

在此题中可以直接设置两个指针fast,slow。让fast指针先向前移动k-1个节点,再一起移动fast和slow指针。当fast遍历完链表之后slow指针指向的就是链表中倒数第k个节点(此题要注意传让的k值是否合理,不然会造成越界访问):

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k) {
    if(pListHead==NULL)
        return pListHead;
    struct ListNode* fast=pListHead,*slow=pListHead;
    for(k-1;k>0;k--)
    {
        if(fast==NULL)
            return NULL;
        fast=fast->next;
    }
    while(fast)
    {
        fast=fast->next;
        slow=slow->next;
    }
    return slow;
}

 此方法只要遍历一次链表时间复杂度为O(n),空间复杂度为O(1):

LeetCode和牛客网经典链表题目合集_第5张图片

LeetCode第21题:合并两个有序链表

题目地址:

 21. 合并两个有序链表

在此题中,我使用了两个指针temp1和temp2分别遍历List1和List2链表,另给一个指针new接收temp1和temp2指针比较后的节点,谁的val值小就将该节点赋给new形成一个新链表: 

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
    if(list1==NULL&&list2==NULL)
        return NULL;
    else if(list1==NULL)
        return list2;
    else if(list2==NULL)
        return list1;
    struct ListNode* new=NULL,*temp1=list1,*temp2=list2;
    if(temp1->val>temp2->val)
    {
        new=temp2;
        temp2=temp2->next;
    }
    else
    {
        new=temp1;
        temp1=temp1->next;
    }
    struct ListNode* head=new;
    while(temp1&&temp2)
    {
        if(temp1->val>temp2->val)
        {
            new->next=temp2;
            temp2=temp2->next;
            new=new->next;
        }
        else
        {
            new->next=temp1;
            temp1=temp1->next;
            new=new->next;
        }
    }
    if(temp1==NULL)
        new->next=temp2;
    else
        new->next=temp1;
    return head;
}

此方法只需要遍历每个链表一次,时间复杂度为O(n),空间复杂度为O(1):

LeetCode和牛客网经典链表题目合集_第6张图片

牛客网:分割链表

题目地址:

链表分割_牛客题霸_牛客网

这道题目在原链表上进行操作将会异常复杂繁琐,所以我们的操作是创建两个带哨兵位的头节点的链表:greater 和 less。(选择创建带哨兵位头节点的链表是为了方便改变头节点下一个节点,否则修改新链表头节点时较麻烦。)greater后面跟大于x的结点,less后面跟小于x的结点。我们只需让less的尾结点接上greater的头结点即可。这样既实现了分割又保证原数据的顺序不被改变:

class Partition {
public:
    ListNode* partition(ListNode* pHead, int x) 
    {
        ListNode* greaterhead, *greatertail, *lesshead, *lesstail;
        greatertail = greaterhead = (ListNode*)malloc(sizeof(ListNode));
        lesstail = lesshead = (ListNode*)malloc(sizeof(ListNode));
        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;
        }
        greatertail->next = NULL;
        lesstail->next = greaterhead->next;
        return lesshead->next;
    }
};

该方法只需要遍历一次链表,时间复杂度为O(n),空间复杂度为O(1):

牛客网:链表的回文结构

题目地址:

链表的回文结构_牛客题霸_牛客网

对于该题我先用快慢指针找到链表的中间节点将其赋予指针slow(在这里要判断链表节点的寄偶数),再将slow之前的节点用迭代法反转形成一个新的链表reverse,接着将reverse链表与slow链表相对应的节点数据值相比较(这里如果整个链表的节点数为奇数,slow要指向下一个节点),如果一一对应就是回文结构了:

class PalindromeList {
  public:
    bool chkPalindrome(ListNode* A) {
        if (A == NULL || A->next == NULL)
            return false;
        ListNode* slow = A, *fast = A, *reverse = NULL, *temp = A, *New = NULL;
        int i = 0;
        while (fast)
        {
            fast = fast->next;
            if (fast == NULL) 
            {
                i++;
                break;
            }
            fast = fast->next;
            slow = slow->next;
        }
        while (temp != slow) 
        {
            New = temp;
            temp = temp->next;
            New->next = reverse;
            reverse = New;
        }
        if (i != 0)
        {
            slow = slow->next;
        }
            
        while (slow) {
            if (slow->val != reverse->val)
                return false;
            slow = slow->next;
            reverse = reverse->next;
        }
        return true;
    }
};

 该方法需要遍历两次链表,时间复杂度为O(n),空间复杂度为O(1):

LeetCode第160题:相交链表

题目地址: 

160. 相交链表

LeetCode和牛客网经典链表题目合集_第7张图片

对于该题我们看图可知,如果两链表相交,它们到最后相同的节点数一定是一样的,那我们不妨计算出每个链表的节点总数,再相减其节点总个数算出差值,接着让节点数多的一条链表先遍历差值个数的节点数,最后两个链表再一起同时遍历,终有一个节点是它们第一次相交的节点:

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    if(headA==NULL||headB==NULL)
        return NULL;
    struct ListNode*h1=headA,*h2=headB;
    int A=0,B=0;
    while(h1)
    {
        A++;
        h1=h1->next;
    }
    while(h2)
    {
        B++;
        h2=h2->next;
    }
    int c=A>B?A-B:B-A;
    if(A>B)
    {
        while(c--)
        {
            headA=headA->next;
        }
    }
    else
    {
        while(c--)
        {
            headB=headB->next;
        }
    }
    while(headA)
    {
        if(headA==headB)
            return headA;
        headA=headA->next;
        headB=headB->next;
    }
    return NULL;
}

LeetCode第141题:环形链表

题目地址:

 141. 环形链表

 LeetCode和牛客网经典链表题目合集_第8张图片

在判断链表结构是否为环形链表时,我们可以用快慢指针的方法。设定fast和slow指针,fast每次走两个节点,slow每次走一个节点,当链表为环形的时,slow和fast都进入了环形结构就变成了追击问题。它们之间的速度差始终为1,只要时间足够它们总能相遇(其他的速度差不能保证它们之间会相遇,具体要看环形结构,所以速度差为1是最保险的方法):

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

LeetCode第142题:环形链表II

题目地址:

142. 环形链表 II

该题在上一题的基础上增加了一个条件:返回链表开始入环的第一个节点。

对此我们可以故技重施,照上一题快慢指针的方法先判断链表是不是环状结构,如果是就返回fast和slow相遇的节点,如果不是则返回空。是环状结构呢,我们将fast和slow相遇的节点“剪断”,即将相遇的节点的next置空(但是这里要提前保存next指针指向的节点,方便我们找链表开始入环的第一个节点)。这样该链表就被“剪”成了一个相交链表,链表的尾节点就是fast和slow相遇的节点,而相交的起始节点就是链表开始入环的第一个节点:

LeetCode和牛客网经典链表题目合集_第9张图片

现在我们就可以看成两个单链表的相交问题了,还是使用LeetCode第160题的方法找到它们相交的节点:

struct ListNode* returnnode(struct ListNode *head)
{  
    struct ListNode*fast=head,*slow=head;
    while(fast&&fast->next)
    {
        fast=fast->next;
        if(fast==slow)
            return fast;
        fast=fast->next; 
        if(fast==slow)
            return fast;
        slow=slow->next;
    }
    return NULL;
}
struct ListNode* findnode(struct ListNode *head,struct ListNode *nextnode)
{
    struct ListNode*h1=head,*h2=nextnode;
    int A=0,B=0;
    while(h1)
    {
        A++;
        h1=h1->next;
    }
    while(h2)
    {
        B++;
        h2=h2->next;
    }
    int c=A>B?A-B:B-A;
    if(A>B)
    {
        while(c--)
        {
            head=head->next;
        }
    }
    else
    {
        while(c--)
        {
            nextnode=nextnode->next;
        }
    }
    while(head)
    {
        if(head==nextnode)
            return head;
        head=head->next;
        nextnode=nextnode->next;
    }
    return NULL;
}
struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode* cutnode=returnnode(head);
    if(cutnode==NULL)
        return NULL;
    struct ListNode* nextnode=cutnode->next;
    cutnode->next=NULL;
    return findnode(head,nextnode);
}

该方法的时间复杂度为O(n),空间复杂度为O(1):

LeetCode和牛客网经典链表题目合集_第10张图片

LeetCode第138题:复制带随机指针的链表 

题目地址:

138. 复制带随机指针的链表

咋一眼看到这个题目感觉好复杂,直接暴力求解,先将整个链表拷贝一份,再慢慢一个一个来还原节点中的random指针:

struct Node* BuySListNode(int x) {
    struct Node* p = (struct Node*)malloc(sizeof(struct Node));
    p->random = NULL;
    p->val = x;
    p->next = NULL;
    return p;
}
void CapyNode(struct Node** pplist, int x) {
    struct Node* newplist = BuySListNode(x);
    if (*pplist == NULL)
    {
        *pplist = newplist;
        return;
    }
    struct Node* tail = *pplist;
    while (tail->next) {
        tail = tail->next;
    }
    tail->next = newplist;
}
void CapyRandom(struct Node** pplist, struct Node* Head, struct Node* node, struct Node* head)
{
    if (node->random == NULL)
    {
        (*pplist)->random = NULL;
        return;
    }
    while (head)
    {
        if (node->random == head)
        {
            (*pplist)->random = Head;
            return;
        }
        head = head->next;
        Head = Head->next;
    }
}
struct Node* copyRandomList(struct Node* head) {
    struct Node* Copy = NULL, * temp = head;
    while (temp) {
        CapyNode(&Copy, temp->val);
        temp = temp->next;
    }
    temp = head;
    struct Node* Head = Copy;
    while (Copy)
    {
        CapyRandom(&Copy, Head, temp, head);
        Copy = Copy->next;
        temp = temp->next;
    }
    return Head;
}

这种方法时间复杂度为O(n^2),不太理想:

LeetCode和牛客网经典链表题目合集_第11张图片

下面我们用另一种方法来将时间复杂度改善到O(n):

比如现在有一原链表:

LeetCode和牛客网经典链表题目合集_第12张图片

每次遍历一个节点将复制开辟的节点链接在原节点的next指针上,再将复制的节点的next指针指向原链表节点的next节点(红色的为新开辟的复制节点):

LeetCode和牛客网经典链表题目合集_第13张图片

接着(只要random不是空指针)在我们再一次遍历原链表时直接找到原节点的random指针,而在random指针指向的节点的next指针就是我们复制节点的random指针所要指向的节点了。如此一来我们就不用一个一个挨个的去找random指针的指向了:LeetCode和牛客网经典链表题目合集_第14张图片

最后再将复制的节点在原链表上取下就完成了复制链表的复制了:LeetCode和牛客网经典链表题目合集_第15张图片

代码如下: 

struct Node* BuySListNode(int x) {
    struct Node* p = (struct Node*)malloc(sizeof(struct Node));
    p->random = NULL;
    p->val = x;
    p->next = NULL;
    return p;
}
void CapyNode(struct Node** pplist) {
    struct Node* newplist = BuySListNode((*pplist)->val);
    newplist->next=(*pplist)->next;
    (*pplist)->next=newplist;
}
void CapyRandom(struct Node*head)
{
    struct Node*list=head;
    while(list)
    {
        if(list->random)
            list->next->random=list->random->next;
        list=list->next->next;
    }
}
struct Node* copyRandomList(struct Node* head) {
    if(head==NULL)
        return NULL;
    struct Node*temp=head;
    while (temp) {
        CapyNode(&temp);
        temp = temp->next->next;
    }
    CapyRandom(head);
    struct Node*copy=(struct Node*)malloc(sizeof(struct Node));
    struct Node*new=copy;
    while(head)
    {
        copy->next=head->next;
        head=head->next->next;
        copy=copy->next;
    }
    return new->next;
}

 该方法需要遍历三次链表,时间复杂度为O(n):

LeetCode和牛客网经典链表题目合集_第16张图片

 


本次的链表题目合集就到此为止了,希望可以帮得上各位~

你可能感兴趣的:(LeetCode,数据结构,链表,leetcode,数据结构)