链表OJ刷题(二)

制作不易,三连支持一下呗!!!

文章目录

  • 前言
  • 一、链表的回文结构
  • 二、相交链表
  • 三、链表中倒数第k个节点
  • 四、环形链表Ⅰ和Ⅱ
  • 总结

前言


一、链表的回文结构

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

这里我们需要先了解一下什么叫做回文:从前向后看与从后向前看的结果是一样的,我们就称为回文结构!!!

思路: 这道题目是我们之前链表OJ(一)中两道经典题目的结合------查找中间节点与链表的逆置。

我们可以先找到链表的中间节点(如果链表节点个数为偶数,有两个中间节点,我们选取靠后的那一个),然后将链表的中间节点以后的节点全部逆置。最后循环遍历原链表的前半部分和逆置得到的后半部分是否相同,如果相同就是回文结构,反之就不是。

我们只需要将这两个步骤分别实现成函数来调用即可

代码实现:

cpp 复制代码
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};
typedef struct ListNode ListNode;
//查找中间节点
struct ListNode* middleNode(struct ListNode* head) {
    ListNode* pfast,*pslow;
    pfast=pslow=head;
    while(pfast&&pfast->next)
    {
         pfast=pfast->next->next;
         pslow=pslow->next;
    }
    return pslow;
}
//逆置所传链表
struct ListNode* reverseList(struct ListNode* head) {
    if(!head)
    {
        return head;
    }
    ListNode*prev,*pcur,*next;
    prev=NULL;
    pcur=head;
    next=head->next;
    while(pcur)
    {
        pcur->next=prev;
        prev=pcur;
        pcur=next;
        if(next)
        next=next->next;
    }
    return prev;
}
class PalindromeList {
public:
    bool chkPalindrome(ListNode* A) {
        // write code here
        if(!A)
        return true;
     ListNode* mid=middleNode(A);
     mid= reverseList(mid);
     //循环遍历两个链表,进行比较
     while(mid)
     {
        if(A->val!=mid->val)
        return false;
        mid=mid->next;
        A=A->next;
     }
     return true;
    }
};

二、相交链表

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

思路一:计算两个链表的长度

步骤:

1.先判断是否相交:

如果两个链表的尾节点相同则一定是相交的,如果连尾节点都不同,则一定不相交。

2.找到相交的起始节点:

先分别通过循环遍历的方式计算出两个链表的长度lenA和lenB,用gap来表示它们之间的差值,让长链表先遍历gap步,以保证此时链表的长度是相同的,最后循环遍历两个链表,如果出现链表A和链表B的节点是相同的,则表示这个节点就是我们要找的节点。

代码实现:

cpp 复制代码
 typedef struct ListNode ListNode;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    int lenA=0,lenB=0;
    ListNode* curA=headA,*curB=headB;
    //计算链表A的长度
    while(curA)
    {
        lenA++;
        curA=curA->next;
    }
    //计算链表B的长度
     while(curB)
    {
        lenB++;
        curB=curB->next;
    }
    //判断尾节点是否相同
    if(curA!=curB)
    {
    return NULL;
    }
    int gap=abs(lenA-lenB);
    ListNode* longlist=headA;
    ListNode* shortlist=headB;
    if(lenB>lenA)
    {
        longlist=headB;
        shortlist=headA;
    }
    //让长链表先走gap步
    while(gap--)
    {
        longlist=longlist->next;
    }
    //循环找相交起始节点
    while(longlist!=shortlist)
    {
        longlist=longlist->next;
        shortlist=shortlist->next;
    }
    return longlist;
}

思路二: 走相同的路,彼此一定会相遇

除了计算链表长度外,我们可以通过两个链表长度和相同的性质来解题

过程:遍历其中一个链表,当到末尾时跳到另一个链表继续遍历,直到再次到达末尾

1.如果链表不相交,则两个遍历指针同时指向NULL,返回NULL即可。

2.如果链表相交,因为两个遍历指针同时达到末尾,向前推理可知,两个指针一定会有同时指向相交的起始节点的时候。

3.如果两个链表等长,也会同时到达相交的起始节点。

代码实现:

这个思路实现起来就比较简洁,但是不容易想到!!!

三、链表中倒数第k个节点

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

思路一:求出链表长度,确定所求节点是第几个节点

思路非常简单

代码实现:

思路二:双指针法

受链表的中间节点一题的启发,我们这里依旧可以使用快慢指针,只需要将快慢指针的距离控制在k,当快指针走到链表末尾时,慢指针正好到达所求节点。

代码实现:

四 、环形链表Ⅰ和Ⅱ

环形链表Ⅰ:

https://leetcode.cn/problems/linked-list-cycle/

思路: 快慢指针法

定义一个快指针,一个慢指针,快指针一次走两步,慢指针一次走一步。如果此链表带环则,快慢两个指针一定会相遇,如果此链表不带环,则fast指针或fast->next最后一定会是NULL。

代码实现:

cpp 复制代码
typedef struct ListNode ListNode;
bool hasCycle(struct ListNode *head) {
    ListNode* fast=head,*slow=head;
    while(fast&&fast->next)//注意:顺序不能调换,否则可能会存在短路的情况
    {
        fast=fast->next->next;
        slow=slow->next;
        if(fast==slow)
        {
            return true;
        }
    }
    return false;
}

接下来我们从数学的角度解释为什么两个指针一定会相遇:

假设上图是一个带环链表 ,当slow也进环时,假设fast与环的入口之间的距离是X,环的长度是C

由于两个指针的速度差是1 ,总有一天fast指针会追上slow指针,并不会错过,并且此时slow指针一定还没有走够一圈(因为X<C),所以我们就证明了一个指针走两步,一个指针走一步一定可以追上!!!

同理 如果一个指针走三步,一个指针走一步,我们也可以用相同的思路来验证是否一定会追上:

答案是一定会。

假设slow进环时两个指针的位置如上图所示。

一.假设此时两个指针的距离N为偶数,则因为每走一步两个指针的距离缩小2,那么在第一轮fast一定可以追上slow(并且此时slow并未转够一圈)!!!

二.如果N为奇数,则第一圈并不能追上,则两个指针的距离在经过第一轮追击之后会变为C-1(C为环的长度)。

1.如果C-1是偶数,则下一轮就一定可以追上。

2.如果C-1是奇数,则永远也追不上!!!

问题是:N为奇数和C-1是奇数是否可以同时成立呢?

我们假设未进环时slow走的距离是L,slow刚进环时slow和fast的距离是N,环的长度是C,fast已经转了x圈。

slow走的距离是:L

fast走的距离是:L+x*C+N

因为fast的速度是slow的3倍,则3L= L+x*C+N

推出:2L=x*C+N

2L一定是偶数,如果C-1和N同时为奇数,则x*C一定是偶数,x*C+N就一定是奇数,两边不可能相等,矛盾!!!

因此只要N为奇数时,fast一定可以在第二轮追击中追上slow

也就是说,不管如何,fast一定能够追上slow!!!

但是由于fast走两步,slow走一步写起来更简单,我们选择这种方法。


总结

环形链表的逻辑推理很重要!!!

相关推荐
爱吃喵的鲤鱼8 分钟前
linux进程的状态之环境变量
linux·运维·服务器·开发语言·c++
懒惰才能让科技进步30 分钟前
从零学习大模型(十二)-----基于梯度的重要性剪枝(Gradient-based Pruning)
人工智能·深度学习·学习·算法·chatgpt·transformer·剪枝
DARLING Zero two♡35 分钟前
关于我、重生到500年前凭借C语言改变世界科技vlog.16——万字详解指针概念及技巧
c语言·开发语言·科技
7年老菜鸡35 分钟前
策略模式(C++)三分钟读懂
c++·qt·策略模式
Ni-Guvara44 分钟前
函数对象笔记
c++·算法
似霰1 小时前
安卓智能指针sp、wp、RefBase浅析
android·c++·binder
芊寻(嵌入式)1 小时前
C转C++学习笔记--基础知识摘录总结
开发语言·c++·笔记·学习
獨枭1 小时前
C++ 项目中使用 .dll 和 .def 文件的操作指南
c++
霁月风1 小时前
设计模式——观察者模式
c++·观察者模式·设计模式
橘色的喵1 小时前
C++编程:避免因编译优化引发的多线程死锁问题
c++·多线程·memory·死锁·内存屏障·内存栅栏·memory barrier