链表 oj2 (7.31)

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

我们通过头插来实现

将链表上的节点取下来(取的时候需要记录下一个节点),形成新的链表,对新的链表进行头插。

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */


struct ListNode* reverseList(struct ListNode* head)
{
       //用cur对原链表进行遍历
       struct ListNode* cur=head,*newhead=NULL;
       //原链表为空,遍历结束
       while(cur)
       {
       //记录cur的下一个节点
       struct ListNode* next=cur->next;
       //cur链接到新链表
       cur->next=newhead;
       //cur成为新链表的头指针
       newhead=cur;
       //cur通过next在原链表中向后移
       cur=next;
       }
       return newhead;
}

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

这里需要引用哨兵位,先介绍一下

哨兵位就是不带数据的头节点,且为固定的节点,当链表为空时,带哨兵位的链表(右)存在一个头节点(空间),而不带哨兵位的链表(左)则没有节点。

更改带哨兵位的链表(增删查改)就不需要判断,通过二重指针改变头指针,通过 next 就能直接实现。

使用的时候为哨兵位申请一块动态内存,作为头节点,结束的时候可以根据题目要求将其释放。

实现:

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
    if(list1==NULL)
    {
        return list2;
    }
    if(list2==NULL)
    {
        return list1;
    }
       struct ListNode* head=NULL,*tail=NULL;
       //带一个哨兵位,方便尾插
       head=tail=(struct ListNode*)malloc(sizeof(struct ListNode));
       while(list1&&list2)
       {
       if(list1->val<list2->val)
       {
               tail->next=list1;
               tail=tail->next;
               list1=list1->next;
       }
       else
       {
               tail->next=list2;
               tail=tail->next;
               list2=list2->next;
       }
       }
       //
       if(list2)
       {
           tail->next=list2;
       }
       if(list1)
       {
           tail->next=list1;
       }
       //哨兵位的头使用完需要释放掉
       struct ListNode* del=head;
       head=head->next;
       free(del);

       return head;
}

链表分割_牛客题霸_牛客网 (nowcoder.com)

思路是创建两个链表,将小于 x 的尾插到第一个链表,大于 x 尾插到第二个链表。

此题目用带哨兵位的链表做更加简单,因为尾插时不用考虑链表是否为空的情况,将两个链表链接的时候也不需要考虑其中一个链表为空的情况了(即不用担心链表为空的情况)。

cpp 复制代码
/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
#include <cstddef>
class Partition {
  public:
    ListNode* partition(ListNode* pHead, int x) {
        struct ListNode* ghead, *gtail, *lhead, *ltail;
        ghead = gtail = (struct ListNode*)malloc(sizeof(struct ListNode));
        lhead = ltail = (struct ListNode*)malloc(sizeof(struct ListNode));
        //用cur遍历
        struct ListNode* cur = pHead;
        while (cur)
        {
        if(cur->val < x)
        {
           ltail->next = cur;
           ltail = ltail->next;
        }
            else
            {
            gtail->next = cur;
            gtail = gtail->next;
            }
            cur = cur->next;
            
        }
        //将低链表的尾链接到高链表的哨兵位之后的节点
        ltail->next = ghead->next;
        //将目标链表的尾置空,否则产生环
        gtail->next = nullptr;
        //拷贝目标链表的头节点
        struct ListNode* head = lhead->next;
        //释放哨兵位
        free(lhead);
        free(ghead);
        return head;
    }
};

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

回文结构就是对称的意思,例如 1 2 2 1,1 2 3 2 1。

结合前面的 oj 题目,我们容易想到一个方法,先找出链表的后半段,然后将其逆置,再将其与前半段比较,如果都相同,则为回文结构。

cpp 复制代码
/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) : val(x), next(NULL) {}
};*/
class PalindromeList {
public:
    //反转链表
    struct ListNode* reverseList(struct ListNode* head)
{
       //用cur对原链表进行遍历
       struct ListNode* cur=head,*newhead=nullptr;
       //原链表为空,遍历结束
       while(cur)
       {
       //记录cur的下一个节点
       struct ListNode* next=cur->next;
       //cur链接到新链表
       cur->next=newhead;
       //cur成为新链表的头指针
       newhead=cur;
       //cur通过next在原链表中向后移
       cur=next;
       }
       return newhead;
}
    //找出中间节点
    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;
}
    bool chkPalindrome(ListNode* head) {
        //找出后半段
        struct ListNode* mid=middleNode(head);
        //将后半段逆置
        struct ListNode* rmid=reverseList(mid);
        while(rmid&&head)
        {
            if(rmid->val!=head->val)
            {
                return false;
            }
            rmid=rmid->next;
            head=head->next;
        }
        return true;
    }
};

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

思路:

1.遍历计算出A,B链表各自的长度 lenA,lenB

2.长的链表走差距步 lenA-lenB,此时两条链表的长度相同

3.同时移动找交点(指针相同),最后返回这个交点。

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    struct ListNode* curA=headA,*curB=headB;
    int lenA=1,lenB=1;
    //提示了链表不为空,因此结尾用下一个节点判断,同时初始长度为1
    while(curA->next)
    {
        curA=curA->next;
        lenA++;
    }
    while(curB->next)
    {
        curB=curB->next;
        lenB++;
    }
    //尾节点不同,一定没有交点
    if(curA !=curB)
    {
        return NULL;
    }
     struct ListNode* longList=headA,*shortList=headB;
     if(lenA<lenB)
     {
        longList=headB;
        shortList=headA;
     }
     //算出差距步(绝对值)
     int gap=abs(lenA-lenB);
     //长的先走差距步
     while(gap--)
     {
         longList=longList->next;
     }
     //同时走找交点,相等就找到了
     while(longList !=shortList)
     {
          longList=longList->next;
          shortList=shortList->next;
     }
     return longList;
}

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

链表有循环或者非循环

循环链表的尾节点指向头节点。

还有一种带环链表,它的尾节点可以指向链表的任意一个节点(包括自己)。

带环链表中的节点会重复出现,我们依然定义一快一慢指针,如果是带环链表,那么快指针一定能追上慢指针,两个指针一定有相等的时候(追及问题);如果不带环,直接就遍历到空了。

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
bool hasCycle(struct ListNode *head) {
     struct ListNode* fast=head,*slow=head;
     //链表可能不为环形
     while(fast&&fast->next)
     {
         fast=fast->next->next;
         slow=slow->next;
         if(slow==fast)
         {
             return true;
         }
     }
     return false;
}

由带环追及问题引发的思考:

1.slow走一步,fast走两步,一定能追上吗,会不会错过?

2.slow走一步,fast走n步(n>=3),一定能追上吗,会不会错过?

如果 slow 走一步,fast 走三步,假设 slow 入环时,slow 和 fast 的距离为 M,每移动(追击)一次,距离缩小2,若 M 为偶数,则当距离减为0时,刚好追上;若 M 为奇数,距离最小时为 -1,即fast 超过了 slow 一步,此时又要观察环的长度C,此时 slow 和 fast 的距离为 C-1,若 C 为奇数,C-1为偶数,那么再经过一轮追击之后就能刚好追上。如果 C 为偶数,那么 C-1 为奇数,奇数-2 永远为奇数,就永远追不上了。

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

分析:

设七点到入口长度:L

环的周长:C

入口点到相遇点的距离:X

fast 走的距离(速度)为 slow 的二倍

slow 进环后的一圈内,fast 一定追上 slow,slow 走的距离为 L+X

slow 进环时,fast 已经走了 n(n>=1) 圈了,fast 走的距离为 L+n*C+X

fast 追赶 slow 之前会先补足 n 圈,
fast 走的距离(速度)为 slow 的二倍可知

2(L+X)=L+n*C+X

同减去L+X

L+X=n*C

L=n*C-X

计算的时候我们默认为 fast 已经跑到 n-1 圈,因此不需要关注 n .

从这个结论我们可以得到从相遇点到环入口点的距离从起点到入口点的距离相同,要想找到入口点,就需要两个指针分别从这两个点开始跑,它们相遇的位置就是入口点。

cpp 复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode* fast,*slow;
    fast=slow=head;
    while(fast&&fast->next)
    {
        slow=slow->next;
        fast=fast->next->next;
        //相遇
        if(slow==fast)
        {
            //记录相遇点
            struct ListNode* meet=slow;
            //各自从相遇点和头节点开始跑,相遇则为入口点
            while(head!=meet)
            {
                head=head->next;
                meet=meet->next;
            }
            return meet;
        }
    }
    //fast或fast的下一个节点为空,说明无环
    return NULL;
}

法2:

将环从相遇点断开,相遇点之后作为一条新的链表,和旧链表一起找相交点,相交点就是入口点。

相关推荐
win x2 小时前
链表(Linkedlist)
数据结构·链表
杜若南星2 小时前
保研考研机试攻略(满分篇):第二章——满分之路上(1)
数据结构·c++·经验分享·笔记·考研·算法·贪心算法
曙曙学编程2 小时前
初级数据结构——栈
数据结构
严文文-Chris2 小时前
【B+树特点】
数据结构·b树
严文文-Chris2 小时前
B-树特点以及插入、删除数据过程
数据结构·b树
欧阳枫落3 小时前
python 2小时学会八股文-数据结构
开发语言·数据结构·python
手握风云-3 小时前
零基础Java第十六期:抽象类接口(二)
数据结构·算法
<但凡.4 小时前
编程之路,从0开始:知识补充篇
c语言·数据结构·算法
f狐0狸x4 小时前
【数据结构副本篇】顺序表 链表OJ
c语言·数据结构·算法·链表
Light604 小时前
低代码牵手 AI 接口:开启智能化开发新征程
人工智能·python·深度学习·低代码·链表·线性回归