单链表里的常见OJ题代码实现

本文介绍了链表的各种操作,包括删除指定元素,反转链表,找到链表的中间结点,寻找倒数第k个节点,合并两个有序链表,分割链表,判断链表的回文结构,找出相交链表的交点,以及检测和处理环形链表问题。每个操作都提供了详细的思路分析和C语言实现代码。

题目均来自于牛客或者力扣

一.移除链表元素

1. 题目描述

在这里插入图片描述

2.思路分析

题目中描述说明删除所有符合条件的节点,因此需要把整个链表都遍历一遍,然后再删除其中的元素,释放内存,但是删除之前,需要把删除位置前的节点指向改变一下。

3.代码实现

/**
 * 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* pre=NULL;
while(cur)
{
    if(cur->val==val)
    {
    //若头节点的元素的值就符合条件
        if(cur==head)
        {
        //需要更新头节点,头节点没有前一个
            head=cur->next;
            free(cur);
            cur=head;
        }
        else
        {
      //更改指向
        pre->next=cur->next;
        free(cur);//释放内存
        cur=pre->next;
        }

    }
    else
    {
     //迭代,pre为cur前面一个节点
        pre=cur;
        cur=cur->next;
    }
}

return  head;
}

二.反转链表

1.题目描述

在这里插入图片描述

2.思路分析

反转链表可以通过改变链表节点的指向,因此从后往前改变指向,需要迭代到最后一个节点,注意链表为空的情况。

3.代码实现

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


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!=NULL)
{
    cur->next =pre;//反转指向
    pre=cur;//更新pre节点的地址
    cur=next;//更新cur节点的地址
    if(next!=NULL)
    {
next=next->next; //迭代遍历
    }
    
}
  return pre;//返回反转完后的地址,该地址为链表的首地址

}

三.链表的中间结点

1.题目描述

在这里插入图片描述
要求只可以遍历链表一次。

2.思路分析

设置两个指针,一个走的快,一个走的慢,快的速度是慢的两倍,当快指针遍历到结束后,慢指针就遍历到链表中间。
需要讨论奇数个结点和偶数个结点的情况
若是奇数结点,当fast->next为NULL时,slow为中间结点
若是偶数结点,当fast为NULL时,slow为第二个中间结点
在这里插入图片描述

3.代码实现

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


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

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

1.题目描述

在这里插入图片描述

2.思路分析

创建两个指针变量,fast先走k步,之后两个指针同时迭代,直到fast一直迭代到尾结点,此时slow就是倒数第k个结点。
注意k为0,则返回NULL
在这里插入图片描述

3.代码实现

/**
 * struct ListNode {
 *  int val;
 *  struct ListNode *next;
 * };
 */

/**
 *
 * @param pListHead ListNode类
 * @param k int整型
 * @return ListNode类
 */
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
    struct ListNode* fast;
    struct ListNode* slow;
    fast = slow = pListHead;
    //讨论特殊情况
    if(k==0)
    return NULL;
    //先走k步
    while (k--) {
        if (fast) {
            fast = fast->next;
        }
        else {
            return NULL;
        }
    }
    //同时迭代
    while (fast) {
        fast = fast->next;
        slow = slow->next;
    }
    return slow;
}

五.合并两个有序链表

1.题目描述

注意是升序链表
在这里插入图片描述
题目 要求只遍历一遍链表

2.思路分析

若在原链表中进行求解,两个循环遍历的暴力解法的时间复杂度为O(n*m)。
考虑到两个链表是升序链表,显然我们需要创建第三个链表,把两个链表的元素按照大小排序尾插到第三个链表中。
注意要考虑到链表为空的情况
在这里插入图片描述

3.代码实现

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


struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){

	//创建新链表的头结点
    struct ListNode*  head=(struct ListNode*)malloc(sizeof(struct ListNode));
    struct ListNode*  tail;//更新尾插
    head=tail=NULL;//初始化
    
    //空链表合并
if(list1==NULL)
{
    return list2;
}
if(list2==NULL)
{
    return list1;
}


	//当其中一个链表遍历走到尾时结束
    while(list1&&list2)
    {
    	//比较两个链表元素的大小
        if(list1->val>list2->val)
        {
            if(head==NULL)
            {
                head=tail=list2;
            }
            else
            {
            //尾插,迭代
            tail->next=list2;
            tail=list2;
            } 
            list2=list2->next;
        }
        else
        {
            if(head==NULL)
            {
            tail=head=list1;
            }
            else
            {
            //尾插,迭代
            tail->next=list1;
            tail=list1;
            }
            list1=list1->next;
        }
    }
    //如果其中一个链表先遍历完了,则直接尾插另外一个链表中的所有元素
    if(list1)
    {
        tail->next=list1;
    }
    if(list2)
    {
        tail->next=list2;
    }
    return head;
}

六.链表分割

1.题目描述

现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。

2.思路分析

将链表分割为两部分,一部分比x高,一部分比x低。
需要创建两个新链表,遍历原链表,一个存储比x高的,一个存储比x低的,链表中元素的相对位置因此不变,之后再拼接两个链表

3.代码实现

struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
class Partition {
  public:
    ListNode* partition(ListNode* pHead, int x) {

        struct ListNode* lesstail, *greattail, *greathead, *lesshead;

	     //创建两个新链表
        lesshead = greattail = (struct ListNode*)malloc(sizeof(struct ListNode));
        greathead = lesstail = (struct ListNode*)malloc(sizeof(struct ListNode));
        //初始化
        lesshead = lesstail;
        greathead = greattail;
        lesstail->next = nullptr;
        greattail->next = nullptr;
    
        struct ListNode* cur = pHead;
    	
        while (cur) {
            if (cur->val < x) {
                lesstail->next = cur;
                lesstail = cur;
            } else {
                greattail->next = cur;
                greattail = cur;
            }
          //遍历 迭代
            cur = cur->next;
        }
        //拼接两个链表
        greattail->next = nullptr;
        lesstail->next = greathead->next;
       //拼接后的链表的新表头
        struct ListNode* newhead = lesshead->next;
        //删除旧表头,释放内存
        free(lesshead);
        free(greathead);
        return newhead;
    }
};

七.链表的回文结构

1.题目描述

在这里插入图片描述

2.思路分析

创建两个指针,第一个指针从头开始遍历到中间结点,第二个指针从中间开始遍历到尾部结点,在第二个指针开始遍历前,需要把第二部分的链表反转一下。之后再遍历。
反转链表函数和取中间结点的链表函数

注意,取中间结点链表,若结点为偶数个,取的是第二个中间结点

**特别地,当结点为奇数个时,两边当遍历到其倒数第二个结点时,遍历的最后一个结点是一样的,之后就遍历到NULL了,因为第一个中间结点的指向并没有改变。

3.代码实现

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/


//反转链表
struct ListNode* reverseList(struct ListNode* head) {
    //链表为空 返回NULL
    if (head == NULL) {
        return NULL;
    }
	
    struct ListNode* pre = NULL;
    struct ListNode* cur = head;
    struct ListNode* next = head->next;



    while (cur != NULL) {
        cur->next = pre;
        pre = cur;
        cur = next1;
        if (next != NULL) {
            next = next->next;
        }

    }
    return pre;

}
//取中间结点链表
struct ListNode* middleNode(struct ListNode* head) {
    struct ListNode* fast, *slow;
    fast = slow = head;
    while (fast && fast->next) {
        fast = fast->next->next;
        slow = slow->next;
    }
    return slow;
}
class PalindromeList {
  public:
    bool chkPalindrome(ListNode* A) {
        struct ListNode* mid = middleNode(A);
        struct ListNode* rhead = reverseList(mid);
        struct ListNode* cur1 = A;
        struct ListNode* cur2 = rhead;
        //同时遍历,直到cur1遍历到头结束
    
        while (cur1 && cur2) {
        //若不同,则不回文返回false
            if (cur1->val != cur2->val) {
                return false;
            } else {
                cur1 = cur1->next;
                cur2 = cur2->next;
            }

        }
        return true;

    }
};

八.相交链表

1.题目描述

在这里插入图片描述

2.思路分析

第一步,判断两个链表是否是相交的
若相交,则返回相交结点
不相交,返回null
判断相交,分别创建两个链表的起始指针—————两个指针遍历到最后的结点是否相同,若相同,则相交。
第二步,如何找到相交结点
假设相交的前提下,记录两个指针走到最后走过的步数,假设A指针走了a步,B指针走了b步,若a>b
则让longlist先走a-b步,之后longlist和shortlist同时走,直到longlist==shortlist为止,此时的longlist或者shortlist就是相交结点。

3.代码实现

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    struct ListNode* tailA;
    struct ListNode* tailB;
    tailA=headA;
    tailB=headB;
    //创建两个变量记录步数
    int a=0;
    int b=0;
    
    while(tailA->next)
    {
        tailA=tailA->next;
        a++;
    }
    while(tailB->next)
    {
        tailB=tailB->next;
        b++;
    }
    //判断是否相交
    if(tailA!=tailB)
    {
        return NULL;
    }
    struct ListNode* shortlist=headA;
    struct ListNode* longlist=headB;
    //判断是headA长,还是headB长
    if(a>b)
    {
        shortlist=headB;
        longlist=headA;
    }
    
    int gap=abs(a-b);
    //长链表先走gap步
    while(gap--)
    {
        longlist=longlist->next;
    }
    //同时走
    while(shortlist!=longlist)
    {
        shortlist=shortlist->next;
        longlist=longlist->next;
    }
    return shortlist;
}

九.环型链表

1.题目描述

在这里插入图片描述

二.思路分析

若存在环,则走的快的指针一定比走的慢的指针在环中相遇
则创建fast和slow指针,fast每次走两步,slow每次走一步
当fast与slow在环中相遇则返回true,若fast走到NULL,则不存在环

三.代码实现

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
bool hasCycle(struct ListNode *head) {
        struct ListNode* fast;
        struct ListNode* slow;
        fast=slow=head;

        while(fast&&fast->next)
        {
            fast=fast->next->next;
            slow=slow->next;
            if(fast==slow)
            {
                return true;
            }
        }
        return false;
}

十.环形链表进阶

1.题目描述

在这里插入图片描述

2.思路分析

假设存在环,环的长度c,入环的第一个结点与环的初始结点长度为l,相遇结点与入环第一个结点相距为a
fast先于slow走到环内,因此当slow与fast相遇后,fast实际上走的是nc+l+a,slow为则为l+a。
可知fast=2slow 推导出l=nc-a=(n-1)c+c-a
在这里插入图片描述

可以知道,相交结点再走c-a步就回到入环的第一个节点了
故让slow与fast相交结点meet与链表头节点cur=head同时走,他们两个的相交结点就是入环的第一个结点

3.代码实现

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode* fast;
    struct ListNode* slow;
    fast=slow=head;
    while(fast&&fast->next)
    {
        fast=fast->next->next;
        slow=slow->next;
		//找fast与slow的相交结点meet
        if(fast==slow)
        {
            struct ListNode* meet=slow;
            //同时迭代
                    while(meet!=head)
                 {
                      head= head->next;
                      meet=meet->next;
                }
                      return meet;   
        }
        
    }
    return NULL;
}

十一.复制带随机指针的链表

1.题目描述

在这里插入图片描述

二.思路分析
难点在于如何复制原链表中的random值,因为我们在复制结点时是无法获得复制链表中的结点的random值,所以要让复制的链表和原链表中存在位置关系。

在这里插入图片描述
1.复制下来的结点插入到原结点与下一个结点中间
2.根据原结点random,处理复制结点的random
3.将复制结点解开,之后形成新链表。

三.代码实现

/**
 * Definition for a Node.
 * struct Node {
 *     int val;
 *     struct Node *next;
 *     struct Node *random;
 * };
 */

struct Node* copyRandomList(struct Node* head) {
	
    struct Node* cur;
    cur=head;

  	 //复制下来的结点插入到原结点与下一个结点中间
    while(cur)
    {
        struct Node* copy=(struct Node*)malloc(sizeof(struct Node));
        
        //copy原结点
        copy->val=cur->val;

        //插入copy结点
        copy->next=cur->next;
        cur->next=copy;

        //迭代
        cur=copy->next;
        
   }
    
    cur=head;
 	 //根据原结点random,处理复制结点的random
    while(cur)
    {
        //复制原结点的random值
        struct Node* copy=cur->next;
        if(cur->random==NULL)
        {
            copy->random=NULL;
        }
        else
        {
            copy->random=cur->random->next;
        }
        //迭代
        cur=copy->next;
    }
    struct Node* copyhead=NULL,*copytail=NULL;
    cur=head;
	//将复制结点解开,之后形成新链表。
    while(cur)
    {
        
        struct Node* copy=cur->next;
        struct Node* next=copy->next;

        //断开原结点与copy结点的连接
        if(copytail==NULL)
        {
            copyhead=copytail=copy;
        }
        else
        {
            copytail->next=copy;
            copytail=copy;
        }
        //迭代
        cur->next=next;
        cur=next;
    }
    return copyhead;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值