链表(三)--链表oj题

目录

链表分割

1.题目链接

2.题目描述

3.题目分析

4.代码实现

链表的中间结点

1.题目链接

2.题目描述

3.题目分析

4.代码实现

合并链表

1.题目链接

2.题目描述

3.题目分析

4.代码实现

反转链表

1.题目链接

2.题目描述

3.题目分析(一定要画图哟!)

4.代码实现

链表中倒数第k个结点

1.题目链接

2.题目描述

3.题目分析

4.代码实现

回文结构

1.题目链接

2.题目描述

3.题目分析

4.代码实现

环形链表(一)

1.题目链接

2.题目描述

3.题目分析

4.代码实现

环形链表(二)

1.题目链接

2.题目描述

3.题目分析

4.代码实现

移除链表元素

1.题目链接

2.题目描述

3.题目分析

4.代码实现

相交链表

1.题目链接

2.题目描述

3.题目分析

4.代码实现

复制带随机指针的链表

1.题目链接

2.题目描述

3.题目分析

4.代码实现


链表分割

1.题目链接

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

2.题目描述

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

3.题目分析

由于他说不能改变原有顺序,因此就想着先搞两条新链表,一边遍历原链表,一边将小于x的放入链表lower,其他的就放入链表greater,最后将lower连接在greater之前即可

4.代码实现

 ListNode* partition(ListNode* pHead, int x) {
        //初始化
        struct ListNode* lowerhead,*lowertail,* greaterhead,* greatertail;
        lowerhead=(struct ListNode*)malloc(sizeof(struct ListNode));
        greaterhead=(struct ListNode*)malloc(sizeof(struct ListNode));
        struct ListNode* cur=pHead;
        lowertail=lowerhead;greatertail=greaterhead;
        lowertail->next=nullptr;greatertail->next=nullptr;
        //遍历
        while(cur!=nullptr){
            if(cur->val<x){  //放入lower
                lowertail->next=cur;
                lowertail=lowertail->next;
            }
            else{    //放入greater
                greatertail->next=cur;
                greatertail=greatertail->next;
            }
            cur=cur->next;
        }
        greatertail->next=nullptr;
        lowertail->next=greaterhead->next;  //连接
        pHead=lowerhead->next;
        //释放这两条链表的表头
        free(lowerhead);
        free(greaterhead);

        return pHead;
    }

链表的中间结点

1.题目链接

876. 链表的中间结点 - 力扣(LeetCode)

2.题目描述

给你单链表的头结点 head ,请你找出并返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点

3.题目分析

做这道题前,我们需要先知道快慢指针算法,其应用范围很广

  • 处理链表或数组的循环问题
  • 找链表中点或需要知道特定元素的位置

快指针每次走两步,慢指针每次走一步,你可以画图感受一下~

  • 如果有偶数个结点,当快指针指向空时,慢指针恰好指向第二个中间结点
  • 如果有奇数个结点,当快指针的next指向空时,慢指针恰好指向中间结点

4.代码实现

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

合并链表

1.题目链接

21. 合并两个有序链表 - 力扣(LeetCode)

2.题目描述

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的

3.题目分析

直接定义一个新头结点,之后按照数值大小轮流遍历原先的两个链表

4.代码实现

struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    struct ListNode* head=NULL,*t=NULL;  //定义新的头结点
    //防止传入的是空结点
    if(list1==NULL){
        return list2; //如果list1是空,就返回list2,不管list2是否为空
    }
    else if(list2==NULL){
        return list1;  //同理
    }
    else{  
        //确定头结点使用谁的结点
        if (list1->val <= list2->val) {  
            head=list1;  
            list1=list1->next;
        }
        else{
            head=list2;
            list2=list2->next;
        }
        //轮流遍历
        t=head;  //t替新头结点进行连接
        while(list1!=NULL&&list2!=NULL){  //有一方遍历完后,就结束循环
            if(list1->val<=list2->val){   //谁小谁就连接在t之后
                t->next=list1;
                t=list1;
                list1=list1->next;
            }
            else{
                t->next=list2;
                t=list2;
                list2=list2->next;
            }
        }
        while(list1){  //如果list1还未遍历完,就将剩下的结点直接连接在t之后
            t->next=list1;
            t=list1;
            list1=list1->next;
        }  
        while(list2){  //同理
            t->next=list2;
            t=list2;
            list2=list2->next;
        }
        t->next=NULL;  //别忘了将新链表的尾结点指向空

        return head;
    }
}

反转链表

1.题目链接

206. 反转链表 - 力扣(LeetCode)

2.题目描述

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表

3.题目分析(一定要画图哟!)

初始化指针

  • newhead不断向后走,最终它停在尾结点上
  • prev来保存上一个结点,通过改变newhead和prev的连接来实现反转
  • 通过next来保存原链表的下一个结点,防止反转连接关系后找不到原结点

经过一轮循环后,第一个和第二个结点的连接成功反转,之后就不断循环该过程,直到next指向空,就完成遍历链表啦~

4.代码实现

struct ListNode* reverseList(struct ListNode* head){
   if(head==NULL){
       return head;
   }
   else{
       struct ListNode* newhead = head, * prev = head, * next = head->next;
	prev->next = NULL;  //注意这里!!!一定要把第一个结点置空,因为该结点时反转链表的最后一个结点,如果不置空,遍历反转链表时会出现问题
	while (next != NULL) {
        //使两个指针往后走
		newhead = next;
		next = next->next;
        //改变指向关系
		newhead->next = prev;
        //使prev往后走
		prev = newhead;
	}
	return newhead;
   }
}

链表中倒数第k个结点

1.题目链接

链表中倒数第k个结点_牛客题霸_牛客网 (nowcoder.com)

2.题目描述

输入一个链表,输出该链表中倒数第k个结点

3.题目分析

这里用到了一点数学知识,就很巧妙的先让fast跑k次,之后继续遍历fast,直到fast指向空(该循环便循环(链表长度-k)次了),因此slow就指向了倒数第k个结点            

4.代码实现

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k) {
	struct ListNode* slow = pListHead, * fast = pListHead;
	if (pListHead == NULL) {  //防止传入的指针是空
		return NULL;
	}
	while (k--) {  //这里不用判断k==0的情况,因为=0时不会进入循环
		if (fast == NULL) {  //如果链表长度不够k,直接返回,防止访问出现错误
			return NULL;
		}
		fast = fast->next;
	}
	while (fast!= NULL) {
		slow = slow->next;
		fast = fast->next;
	}
	return slow;
}

回文结构

1.题目链接

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

2.题目描述

对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。

给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。

3.题目分析

判断回文,就是需要将该链表分成两部分,将其中一部分的链表反转(前面实现过哦),然后判断两部分的结点数值是否相同

而分成两部分就应该找到中间结点,如果为偶数个结点,就找到它第二个结点(前面实现过哦)

4.代码实现

struct ListNode* findmid(struct ListNode* head){
        struct ListNode* slow=head,*fast=head;
        while(fast!=nullptr&&fast->next!=nullptr){
            slow=slow->next;
            fast=fast->next->next;
        }
        if(fast==nullptr){
            return slow;
        }
        slow=slow->next;
        return slow;
    }
    struct ListNode* reverse(struct ListNode* p){
        struct ListNode*prev=p,*next=p->next,*newhead=p;
        newhead->next=nullptr;
        while(next!=nullptr){
            newhead=next;
            next=next->next;
            newhead->next=prev;
            prev=newhead;
        }
        return newhead;
    }
    bool chkPalindrome(ListNode* A) {
        struct ListNode* mid=findmid(A);  //找到中间结点
        struct ListNode* remid=reverse(mid);  //反转包括中间结点之后的链表
        while(remid){  //将反转后的链表进行遍历
            if(A->val!=remid->val){  //与传进来的A进行比较
                return false;
            }
            A=A->next;
            remid=remid->next;
        }
        return true;
    }

环形链表(一)--判断

1.题目链接

141. 环形链表 - 力扣(LeetCode)

2.题目描述

给你一个链表的头节点 head ,判断链表中是否有环, 如果链表中存在环 ,则返回 true 。 否则,返回 false 

3.题目分析

这里又要用到快慢指针啦!

因为fast走的快,但如果遍历的过程中,slow超过了fast,则证明存在环

4.代码实现

bool hasCycle(struct ListNode *head) {
    if(head==NULL||head->next==NULL){ //防止传入的是空指针||只有一个结点(一个结点不能成环)
        return false;
    }
    struct ListNode *slow=head,*fast=head;
    while(slow<=fast){
        if(fast==NULL||fast->next==NULL){  //中途如果fast指向空,则说明没有环
            return false;
        }
        slow=slow->next;
        fast=fast->next->next;
    }
    return true;
}

环形链表(二)--返回入环的第一个节点

1.题目链接

142. 环形链表 II - 力扣(LeetCode)

2.题目描述

给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。不允许修改 链表。

3.题目分析

看的题解哈~

  • 这种问题一般都要用快慢指针,当fast和slow相遇时:此时fast比slow恰好多走了一个环的距离(可以自己画个带环链表比划比划),由于fast每次走两步,slow走一步,因此fast步数=2*slow步数

  • 当slow再走 从头结点到入环第一个结点 的距离的话,slow就恰好指向入环的第一个结点了
  • 那么,如何得到这段距离呢?就再使用双指针,当新指针与slow相遇时,就说明他俩都已经走了这段距离了
  • slow-新指针=c环

4.代码实现

struct ListNode *detectCycle(struct ListNode *head) {
    if(head==NULL||head->next==NULL){  //防止传入的是空指针||只有一个结点(一个结点不能成环)
        return NULL;
    }
    struct ListNode *slow=head,*fast=head;
    while(1){
        if(fast==NULL||fast->next==NULL){  //如果fast可以指向空,说明没有环
            return NULL;
        }
        slow=slow->next;
        fast=fast->next->next;
        if(slow==fast){  //相遇时跳出循环
            break;
        }
    }
    struct ListNode *t=head;  //新指针
    while(t!=slow){
        t=t->next;
        slow=slow->next;
    }
    return t;
}

移除链表元素

1.题目链接

203. 移除链表元素 - 力扣(LeetCode)

2.题目描述

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 

3.题目分析

  • 先将head遍历到第一个数值!=val的结点,防止头结点开始就应该删除
  • 定义一个p指针作为head的副本,将p遍历,寻找数值不等于val的结点进行连接,以及将等于的结点释放掉

4.代码实现

struct ListNode* removeElements(struct ListNode* head, int val){
    while(head!=NULL&&head->val==val){  //找到适合的新链表起点
        head=head->next;
    }
    struct ListNode* p=head;
    while(p!=NULL&&p->next!=NULL){   
        //这里如果不设置p->next!=NULL,那么在下面的代码中,由于会访问p->next->val
        //如果p->next为空,则会发生访问错误
        if(p->next->val==val){  //通过p查找p的下一个结点是否数值==val
                struct ListNode* q=p->next;  //q此时为要删除的结点
                p->next=q->next;
                free(q);
        }
        else{  
                p=p->next;
        } 
        //如果下一个点q->val为val,则p会和q连接,在这种情况下我们并不需要移动p
        //因为p->next已经改变了,需要重新判断一下
    }
    return head;
}

相交链表

1.题目链接

160. 相交链表 - 力扣(LeetCode)

2.题目描述

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null

3.题目分析

  • 这个我也看的题解捏(菜菜,哭哭)
  • 就还是要设置双指针,而我们的目的就是要让双指针共同走到交汇的结点处,也就是说他俩走过的距离要相等
  • 因为两条链表的长度不一定相等,但自交点之后的链表长度就相同了,也就是说需要双指针分别跑完自己的链表 + 对方链表 在相交结点之前 的结点

4.代码实现

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    if(headA==NULL||headB==NULL){  //如果一方为空,则直接返回
        return NULL;
    }
    struct ListNode * n1=headA,*n2=headB;
    while(n1!=n2){
        n1=(n1==NULL?headB:n1->next);  //当遍历完成后,就赋值为对方的头结点
        n2=(n2==NULL?headA:n2->next);
    }
    return n1;
}

复制带随机指针的链表

1.题目链接

138. 复制带随机指针的链表 - 力扣(LeetCode)

2.题目描述

3.题目分析

  • 这个题应该是本篇最复杂的题了,如果盲目处理,很容易出现问题,因为新链表不能指向原链表的结点,还需要处理随机指针
  • 还是看的题解哈~
  • 在每个原结点之后插入和原节点数值相同的新结点,在遍历的同时,访问到原结点的random时,就将新结点的random与原结点的random->next连接;如果random为空,新结点也设为空
  • 处理完随机指针后,就需要将所有的新结点连接成一条链表,并且恢复原链表的连接

4.代码实现

struct Node* copyRandomList(struct Node* head) {
    if (head == NULL) {
        return NULL;
    }
	struct Node* cur=head;
    while(cur!=NULL){  //将每个结点拷贝一份连接在后面
        struct Node* node=(struct Node*)malloc(sizeof(struct Node));
        node->val=cur->val;
        struct Node* next=cur->next;
        cur->next=node;
        node->next=next;
        cur=next;
    }
    //将随机指针赋给新开辟的结点
    struct Node* newhead=NULL,*newtail=NULL,*next=NULL,*newnode=NULL;
    cur=head;
    while(cur!=NULL){  //原链表的结点
        newnode=cur->next;  //新链表的结点
        next=newnode->next;  //原链表的下一个结点
        if(cur->random==NULL){
            newnode->random=NULL;
        }else{
            newnode->random=(cur->random)->next; 
        }
        cur=next;
    }
    //恢复原链表的连接
    cur=head;   //原链表的结点
    while(cur!=NULL){
        newnode=cur->next; //新链表的结点
        next=newnode->next; //原链表的下一个结点
        if(newhead==NULL){
            newhead=newtail=newnode; //如果还没有初始化,就直接赋值
        }else{
            newtail->next=newnode;  //尾插
            newtail=newnode;
        }
        cur->next=next;  //跳过新结点
        cur=next;
    }
    newtail->next=NULL;
    return newhead;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值