数据结构链表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;
}

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

相关文章
|
7天前
|
Java
java数据结构,双向链表的实现
文章介绍了双向链表的实现,包括数据结构定义、插入和删除操作的代码实现,以及双向链表的其他操作方法,并提供了完整的Java代码实现。
java数据结构,双向链表的实现
|
29天前
|
存储 Java 索引
【数据结构】链表从实现到应用,保姆级攻略
本文详细介绍了链表这一重要数据结构。链表与数组不同,其元素在内存中非连续分布,通过指针连接。Java中链表常用于需动态添加或删除元素的场景。文章首先解释了单向链表的基本概念,包括节点定义及各种操作如插入、删除等的实现方法。随后介绍了双向链表,说明了其拥有前后两个指针的特点,并展示了相关操作的代码实现。最后,对比了ArrayList与LinkedList的不同之处,包括它们底层实现、时间复杂度以及适用场景等方面。
42 10
【数据结构】链表从实现到应用,保姆级攻略
|
2月前
|
存储 C语言
【数据结构】c语言链表的创建插入、删除、查询、元素翻倍
【数据结构】c语言链表的创建插入、删除、查询、元素翻倍
【数据结构】c语言链表的创建插入、删除、查询、元素翻倍
|
2月前
|
存储 Java 程序员
"揭秘HashMap底层实现:从数组到链表,再到红黑树,掌握高效数据结构的秘密武器!"
【8月更文挑战第21天】HashMap是Java中重要的数据结构,采用数组+链表/红黑树实现,确保高效查询与更新。构造方法初始化数组,默认容量16,负载因子0.75触发扩容。`put`操作通过计算`hashCode`定位元素,利用链表或红黑树处理冲突。`get`和`remove`操作类似地定位并返回或移除元素。JDK 1.8优化了链表转红黑树机制,提升性能。理解这些原理能帮助我们更高效地应用HashMap。
34 0
|
2月前
|
存储 算法
【初阶数据结构篇】顺序表和链表算法题
此题可以先找到中间节点,然后把后半部分逆置,最近前后两部分一一比对,如果节点的值全部相同,则即为回文。
|
2月前
|
存储 测试技术
【初阶数据结构篇】双向链表的实现(赋源码)
因为头结点的存在,plist指针始终指向头结点,不会改变。
|
2月前
|
存储 测试技术
【初阶数据结构篇】单链表的实现(附源码)
在尾插/尾删中,都需要依据链表是否为空/链表是否多于一个节点来分情况讨论,目的是避免对空指针进行解引用造成的错误。
|
2月前
|
算法
【数据结构与算法】共享双向链表
【数据结构与算法】共享双向链表
13 0
|
2月前
|
算法
【数据结构与算法】双向链表
【数据结构与算法】双向链表
13 0
|
2月前
|
算法
【数据结构与算法】循环链表
【数据结构与算法】循环链表
15 0