数据结构链表oj讲解(上)

简介: 数据结构链表oj讲解

反转单链表


1、题目描述


leetcode:206、反转单链表


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


示例:


1669250586054.jpg


输入:head = [1,2,3,4,5]

输出:[5,4,3,2,1]


这道题目比较暴力的算法是新创建一个链表,把旧链表的数据一个一个挪下来,进行头插。


这样就可以解答出来,不过这种方式的时间复杂度是0(N),空间复杂度O(N),那么我们是否可以简化一下复杂度呢?很显然我们要想反转链表,遍历一遍是必须的,所以我们是否可以节省空间,对原链表下手,而不开辟新的空间呢?答案是肯定的。


2、解:三指针反转单链表


1669250601743.jpg


只需要注意一下最后需要返回的头是什么就可以了。


struct ListNode* reverseList(struct ListNode* head)
{
   struct ListNode* prev=NULL;
   struct ListNode* cur=head;
   struct ListNode* next=NULL;
   if(head==NULL)
   {
       return NULL;
   }
   while(cur)
   {
       next=cur->next;
       cur->next=prev;
       prev=cur;
       cur=next;
   }
   return prev;
}


合并两个有序链表


1、题目描述


leetcode:21、合并两个有序链表


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


1669250630816.jpg


两个指针对两个链表进行遍历,小的尾插,即可。需要注意的是当一个链表结束的时候另一个链表还没结束的处理。


2、解:


带哨兵位的链表进行解答在找头节点的时候比较方便。


struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
    struct ListNode* guard=(struct ListNode*)malloc(sizeof(struct ListNode));
    guard->next=NULL;
    struct ListNode* cur1=list1;
    struct ListNode* cur2=list2;
    struct ListNode* cur=guard;
    while(cur1&&cur2)
    {
        if(cur1->val<cur2->val)
        {
            cur->next=cur1;
            cur1=cur1->next;
        }
        else
        {
            cur->next=cur2;
            cur2=cur2->next;
        }
        cur=cur->next;
    }
    while(cur1)
    {
        cur->next=cur1;
        cur1=cur1->next;
        cur=cur->next;
    }
     while(cur2)
    {
        cur->next=cur2;
        cur2=cur2->next;
        cur=cur->next;
    }
    struct ListNode* newhead=guard->next;
    free(guard);
    guard=NULL;
    return newhead;
}


链表的回文结构


1、题目描述


牛客:(OR36 链表的回文结构)


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


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


测试样例:


1->2->1->2   返回: true


1->2->3->2->1 返回: true


1->2->3->4->2 返回: false


对于回文类的题目,比较快捷的方式就是找到链表的中间节点,对后半段链表进行翻转,然后和前半段链表进行比较,这种方式是比较便捷的。


这里还有一个问题,如何查找链表的中间节点,可能有的方式是遍历计数,然后找中间节点,这种方式是比较低效的,博主想给大家介绍的方式是采用快慢指针的方式查找中间节点。


演示:


1669250666522.jpg


通过观察可以发现,快慢指针对于奇数个和偶数个的处理不一致,只需添加限定条件即可。


找到中间节点后,反转链表我放在上面了,可以直接拿来用,所以,看代码:


class PalindromeList {
public:
     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;
        }
        struct ListNode* reverseList(struct ListNode* head)
        {
            struct ListNode* cur=head;
            struct ListNode* prev=NULL;
            struct ListNode* next=NULL;
            while(cur)
            {
                next=cur->next;
                cur->next=prev;
                prev=cur;
                cur=next;
            }
            return prev;
        }
    bool chkPalindrome(ListNode* A) {
        // write code here
        //快慢指针找到一半,然后前后比较
       struct ListNode* mid=middleNode(A);
       struct ListNode* rmid=reverseList(mid);
        struct ListNode* cur=A;
        while(cur&&rmid)//奇数个和偶数个都要考虑到
        {
            if(cur->val!=rmid->val)
                return false;
            cur=cur->next;
            rmid=rmid->next;
        }
        return true;
    }
};


链表中倒数第k个节点


1、题目描述


牛客:(链表中倒数第k个节点)


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


输入:1,{1,2,3,4,5}


返回值:{5}


如果掌握了快慢指针的精髓,那么这道题目对于大家来说是非常轻而易举的,因为这道题目让找到倒数第k个节点,这个节点和最后一个节点相差的不就是k,所以慢指针和快指针相差k,而快指针走到尾不就行了?不过这道题目还有很多细节的地方需要讲述。


struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) 
{
    struct ListNode* fast=pListHead;
    struct ListNode* slow=pListHead;
    if(fast==NULL)
        return NULL;
    while(k--)
    {
        fast=fast->next;
        if(k>0&&fast==NULL)
            return NULL;
    }
    while(fast)
    {
        slow=slow->next;
        fast=fast->next;
    }
    return slow;
}

这段代码是可以跑过去的,不过可读性比较差,还可以优化。


主要问题是在这两处:

1669250716427.jpg

这处代码主要考虑有可能为NULL的问题。


1669250726183.jpg


这段代码为什么说它可读性差呢?主要就是k>0这部分让人摸不着头脑。为什么会这样写呢?这也是为了解决一些特殊情况:

如果k是恰好为这个链表的总长度,那么倒数第k个节点势必是第一个节点,但是这时fast已经指向NULL,为了则会返回NULL,这显然是错误的,但是这句代码是为了解决k大于整个链表长度时倒数第k个节点为NULL的情况,显然不能删,只好更改为这样,有没有更好的解决办法?答案肯定是有的:

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) 
{
    // write code here
    //快慢指针
    //找倒数第四个
    struct ListNode* fast=pListHead;
    struct ListNode* slow=pListHead;
    if(pListHead)
    {
             while(k--)
        {
                 if(fast==NULL)
                 return NULL;
                 fast=fast->next;
        }
        while(fast)
        {
            fast=fast->next;
            slow=slow->next;
        }
    }
    return slow;
}

这样处理对代码进行了进一步的优化。

相关文章
|
20天前
|
存储 C语言
【数据结构】手把手教你单链表(c语言)(附源码)
本文介绍了单链表的基本概念、结构定义及其实现方法。单链表是一种内存地址不连续但逻辑顺序连续的数据结构,每个节点包含数据域和指针域。文章详细讲解了单链表的常见操作,如头插、尾插、头删、尾删、查找、指定位置插入和删除等,并提供了完整的C语言代码示例。通过学习单链表,可以更好地理解数据结构的底层逻辑,提高编程能力。
48 4
|
21天前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
21天前
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
1月前
|
存储 Java
数据结构第三篇【链表的相关知识点一及在线OJ习题】
数据结构第三篇【链表的相关知识点一及在线OJ习题】
26 7
|
1月前
|
算法 Java
数据结构与算法学习五:双链表的增、删、改、查
双链表的增、删、改、查操作及其Java实现,并通过实例演示了双向链表的优势和应用。
17 0
数据结构与算法学习五:双链表的增、删、改、查
|
20天前
|
C语言
【数据结构】双向带头循环链表(c语言)(附源码)
本文介绍了双向带头循环链表的概念和实现。双向带头循环链表具有三个关键点:双向、带头和循环。与单链表相比,它的头插、尾插、头删、尾删等操作的时间复杂度均为O(1),提高了运行效率。文章详细讲解了链表的结构定义、方法声明和实现,包括创建新节点、初始化、打印、判断是否为空、插入和删除节点等操作。最后提供了完整的代码示例。
39 0
|
1月前
|
存储
[数据结构] -- 双向循环链表
[数据结构] -- 双向循环链表
23 0
|
1月前
|
存储
探索数据结构:便捷的双向链表
探索数据结构:便捷的双向链表
|
1月前
|
存储
探索数据结构:单链表的实践和应用
探索数据结构:单链表的实践和应用
|
1月前
|
算法 Java
数据结构与算法学习六:单向环形链表应用实例的约瑟夫环问题
这篇文章通过单向环形链表的应用实例,详细讲解了约瑟夫环问题的解决方案,并提供了Java代码实现。
21 0