鼠鼠带你玩转链表OJ题(操作链表型+判断结构型+求特殊节点型)

今天鼠鼠带你玩转链表~

你间歇性的努力和蒙混过日子,都是对之前努力的清零!

目录

前言

 一、操作链表型OJ

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

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

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

1.4 方法总结

二、查找链表指定位置节点

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

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

2.3 方法总结

三、判定链表结构

3.1 链表的回文结构_牛客题霸_牛客网 (nowcoder.com)

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

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

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

3.5 方法总结 

四、总结


前言

如果你不熟悉链表概念和链表基本的代码,我写了另一篇博客:顺序表和链表 让你了解

 一、操作链表型OJ

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

 思路一:遍历链表头插至一个新链表

struct ListNode* reverseList(struct ListNode* head) {
    //创建哨兵节点,其指向新链表头节点
    struct ListNode*guard=(struct ListNode*)malloc(sizeof(struct ListNode));
    guard->next=NULL;
    //创建cur遍历原链表
    struct ListNode*cur=head;
    while(cur)
    {
        //先保存原链表下一个节点
        struct ListNode*next=cur->next;
        //头插至新链表  
        cur->next=guard->next;
        guard->next=cur;
        //迭代
        cur=next;
    }
    //返回新链表头节点,释放哨兵节点
    head=guard->next;
    free(guard);
    return head;
}

思路二:双指针遍历链表(后项变前项)

struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode* prev = NULL;//前项
    struct ListNode* curr = head;//<-中间->
    while (curr) {
        struct ListNode* next = curr->next;//保存后项
        curr->next = prev;//反转
        prev = curr;//中间变前项
        curr = next;//后项变中间
    }
    return prev;
}

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

 

思路:本题要复制复杂链表,将这些结点复制链接起来不难。难点在于如何找到新创链表结点的random,有人会说遍历一遍,找到val相同的就是random指向的结点,这个观点是不对的!因为你不知道是否不止一个结点的val是这个值! 怎么办捏?最好的办法就是在原链表的每个结点的后面链接一个相同val的结点,通过原链表random结点,找到我们要找的random!然后再取出新链表,重新链接原链表

struct Node* copyRandomList(struct Node* head) {
	struct Node*cur=head;
    struct Node*copy=NULL;
    struct Node*next=NULL;
    while(cur)
    {
        //复制链接
        next=cur->next;
        struct Node*copy=(struct Node*)malloc(sizeof(struct Node));
        copy->val=cur->val;
        cur->next=copy;
        copy->next=next;
        //迭代
        cur=next;
    }
    //更新copy->random
    cur=head;
    while(cur)
    {
        copy=cur->next;
        if(cur->random==NULL)
        {
            copy->random=NULL;
        }
        else{
            copy->random=cur->random->next;
        }
        cur=copy->next;
    }
    //解下copy链表,恢复原链表
    cur=head;
    struct Node*newhead=NULL;
    struct Node*tail=NULL;
    while(cur)
    {
        copy=cur->next;
        next=copy->next;
        if(newhead==NULL)
        {
            newhead=tail=copy;
        }
        else{
        tail->next=copy;
        tail=tail->next;
        }
        //恢复原链表
        cur->next=next;
        //迭代
        cur=next;
    }
    return newhead;
}

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

思路一:创建新链表尾插非val节点 

struct ListNode* removeElements(struct ListNode* head, int val){
//创建哨兵节点,哨兵指向新链表头节点
struct ListNode*guard=(struct ListNode*)malloc(sizeof(struct ListNode));
struct ListNode*tail=guard;
guard->next=head;
//遍历原链表
while(tail&&tail->next)
{
    //相等,说明此时的next节点不是我们要找的,更新自己的next
    if(tail->next->val==val)
    {
        tail->next=tail->next->next;
    }
    //不同则迭代
    else{
    tail=tail->next;
    }
}
//返回头节点,销毁guard
head=guard->next;
free(guard);
return head;
}

思路二:双指针遍历链表,一个指针寻找非val,一个指针保存上一个非val负责串连

struct ListNode* removeElements(struct ListNode* head, int val){
//寻找非val结点
while(head)
{
    if(head->val==val){
    head=head->next;
}
else{
    break;
}
}
//判定是否为空链表+判断是否链表值全是Val
if(head==NULL)
{
    return NULL;
}

struct ListNode* p1=head;//用于串联新链表
struct ListNode* p2=head->next;//用于寻找非Val结点
while(p2)
{
   if(p2->val!=val)//判定是否为Val结点
   {
        //串联
       p1->next=p2;
       p1=p1->next;
       //继续寻找
       p2=p2->next;
   }
   else{
        //继续寻找
       p2=p2->next;
   }
}
p1->next=NULL;//完成串联
return head;
}

1.4 方法总结

对于操作链表大致分为两种方法,一种是遍历,一种是递归,本篇文章主要围绕遍历来写。遍历其中也可以分为两种主要方法,一种是创建新链表(带哨兵节点),一种是双指针遍历串连,其中带哨兵节点更为简单方便,我主推这种方式解决问题。


二、查找链表指定位置节点

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

struct ListNode* middleNode(struct ListNode* head){
struct ListNode*slow=head,*fast=head;
while(fast&&fast->next)
{
    slow=slow->next;//慢指针走一步
    fast=fast->next->next;//快指针走两步
}
return slow;
}

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

/*
解题思路:
快慢指针法 fast, slow, 首先让fast先走k步,然后fast,slow同时走,fast走到末尾时,slow走到倒数第k个节点。
*/
class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
        struct ListNode* slow = pListHead;
        struct ListNode* fast = slow;
        while(k--)
        {
            if(fast)
                fast = fast->next;
            else
                return NULL;
        }
         
        while(fast)
        {
            slow = slow->next;
            fast = fast->next;
        }
         
        return slow;
    }
};

2.3 方法总结

对于特殊节点,我们主要用快慢指针,步数根据题目来灵活判断,走的次数也是灵活判断!


三、判定链表结构

3.1 链表的回文结构_牛客题霸_牛客网 (nowcoder.com)

思路:1.找到中间节点 -> 2.反转中间节点以后的节点 -> 3.双指针一个从头一个从中间节点开始走,遍历链表,遇到不等停止返回false,当中间节点指针遍历到终点,结束返回true。

 

struct ListNode* middleNode(struct ListNode* head){
struct ListNode*slow=head,*fast=head;
while(fast&&fast->next)
{
    slow=slow->next;//慢指针走一步
    fast=fast->next->next;//快指针走两步
}
return slow;
}
struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode* prev = NULL;
    struct ListNode* curr = head;
    while (curr) {
        struct ListNode* next = curr->next;
        curr->next = prev;
        prev = curr;
        curr = next;
    }
    return prev;
}

    bool chkPalindrome(ListNode* phead) {
        struct ListNode*MiddleNode=middleNode(phead);//找中间结点
        MiddleNode=reverseList(MiddleNode);//反转中间结点以后结点
        //一个从头走,一个从中间结点走,比较结点val
        while(MiddleNode)
        {
            if(phead->val!=MiddleNode->val)//两者不同则直接返回false
            {
                return false;
            }
            else{
                //迭代
                phead=phead->next;
                MiddleNode=MiddleNode->next;
            }
        }
        //全部遍历完返回true
        return true;
    }

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

 思路:如果两个相同长度链表同时走,如果能相遇同一结点,则一定有相交节点!

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    int lenA=0,lenB=0;
    struct ListNode *curA=headA,*curB=headB;
    //分别遍历链表求长度
    while(curA)
    {
        lenA++;
        curA=curA->next;
    }
    while(curB)
    {
        lenB++;
        curB=curB->next;
    }
    //复原指针
    curA=headA;
    curB=headB;
    //长链表先走差距长度,使两链表长度相同
    int Difference=abs(lenA-lenB);
    while(Difference--)
    {
        if(lenA>lenB)
        {
            curA=curA->next;
        }
        else{
            curB=curB->next;
        }
    }
    //二者同时走,若遇到相同结点则返回该节点,不同继续遍历
    while(curA)
    {
        if(curA==curB)
        {
            return curA;
        }
        else{
            curA=curA->next;
            curB=curB->next;
        }
    }
    //两者都走到空则没有相同结点返回空
    return NULL;
}

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

 

//利用物理相对原理,我们可是快指针一次走两步,慢指针一次走一步,两者一定会相遇
//假设慢指针刚进入环时,两者相距N步,将慢指针看作惯性系(看作静止),
//快指针相对于慢指针每次走一步,那么一步步走两者一定会相遇~
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(fast==slow)
        {
            return true;
        }
    }
    return false;
}

有人会问如果快指针一次走三步或者更多,两者会相遇吗?

答案是可能会,但有些情况不会相遇!

下面用fast一次走三步举例哪种情况不会相遇


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

struct ListNode * hasCycle(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)
        {
            return slow;
        }
    }
    return NULL;
}
struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode *meet=hasCycle(head);
    if(meet)
    {
        while(meet!=head)
        {
            meet=meet->next;
            head=head->next;
        }
        return meet;
    }
    return NULL;

}

3.5 方法总结 

对于判断链表结构问题,我们主要在研究链表特性上下功夫,找突破口。这类问题相比于前面的问题,更加注重思想,题目的解题方法也不是千篇一律,而是灵活动态。我认为做这类题要多积累特别的思想,多会找突破口。


四、总结

1.对于链表问题,它的节点之间逻辑联系很复杂,有时指针关系很容易令人头脑发热,建议写链表题目能多开辟变量尽量多开辟,以免自己绕糊涂了。

2.建立新链表,记得能带哨兵一定带哨兵,这样可以有效解决head不停的变动带来的麻烦。


如果这篇文章对你有用,希望你给一个三连~~~

评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值