leetcode | 链表

文章介绍了链表的基本知识,包括线性表的顺序存储和链式存储结构,比较了顺序存储和单链表在查找、插入删除操作上的效率。接着讲解了单链表、静态链表、循环链表和双向链表的特点。还提供了三道链表相关的算法题,分别是两数相加、奇偶链表重组和相交链表的解题思路。
摘要由CSDN通过智能技术生成

01 链表知识点

1.1 基础知识

  • 线性表:零个或多个相同类型的数据元素的有限序列,一对一关系,所含数据元素个数n称为线性表的长度。

  • 线性表有两种物理结构(存储结构):顺序存储、链式存储

    • 线性表的顺序存储结构:用一段地址连续的存储单元依次存储线性表的数据元素。
      • 顺序存储结构三个属性:起始位置;线性表最大存储容量(数组长度MaxSize);线性表当前长度(length)。
    • 线性表的链式存储结构:用一组任意的存储单元存储线性表的数据元素,为了表示数据元素ai与其直接后继数据元素ai+1之间的逻辑关系,对ai来说,除了存储本身的信息之外,还要存储一个指示其直接后继元素存储位置的信息。
      • 存储数据元素信息的域叫数据域;存储直接后继元素位置的域叫指针域,指针域中存储的信息叫指针或链。
        • p结点的数域可以表示为p->data,存放的是p结点的数据元素;指针域可以表示为p->next,存放的是p结点直接后继结点的指针;*L表示链表L的头结点
      • 数据域和指针域组成的数据元素ai的存储映像叫结点(Node),n个结点链结成一个链表,即为线性表的链式存储结构

1.2 顺序存储结构 v.s. 单链表结构

顺序存储结构:

  1. 查找O(1),插入删除O(n)
  2. 需预分配存储空间,分大了浪费,分小了易溢出
  3. 若线性表需频繁查找,很少插入删除,用顺序结构

单链表结构:

  1. 查找O(n),插入删除在找到插入删除结点指针后O(1)
  2. 不需预分配存储空间,元素个数不受限
  3. 若线性表需频繁插入删除,用单链表结构
  4. 线性表元素个数未知或变化较大时,用单链表

链表只能顺序访问元素,要读取第十个元素,必须先读取前九个元素;顺序存储(数组)可以随机访问元素。

1.3 单链表

若链表中的每个结点只包含一个指针域,叫单链表。
单链表
单链表插入删除数据的时间复杂度也为O(n),但对于一次插入删除多个元素也是O(n)(顺序存储每次都是O(n)),因此对于插入删除较频繁的应用,单链表效率更高。

1.4 静态链表

用数组代替指针描述链表,用数组描述的链表叫静态链表。静态链表是给没有指针的高级语言设计的实现单链表能力的方法。
静态链表

  • 优点:插入删除操作时,只需修改游标不需移动元素,改进了顺序存储结构的插入删除操作需移动大量元素的缺点;
  • 缺点:没有解决连续存储分配带来的表长难以确定的问题;失去了顺序存储结构随机存取的特性。

1.5 循环链表

将单链表中终端结点的指针端由空指针改为指向头结点,使整个单链表形成一个环,这种头尾相接的单链表叫单循环链表,简称循环链表。

循环链表解决了如何从任一结点出发访问到链表的全部结点的问题。

1.6 双向链表

双向链表是在单链表各结点中,再设置一个指向其前驱结点的指针域(前驱指针*prioir,后继指针*next),双向链表也可以是循环表。

双向链表为某结点的前后结点操作带来方便,可以提升算法的时间性能,但由于每个结点都要记录两份指针,占用了空间,相当于用空间换时间。

注意,双向链表在插入删除时,需要更改两个指针变量,注意更改顺序。

  • 插入:1.搞定插入结点的前驱后继 2.搞定后结点的前驱 3.搞定前结点的后继。
  • 删除:1.把待删除结点的后结点赋值给前结点的后继 2.把待删除结点的前结点赋值给后结点的前驱。

02 算法题

2.1 两数相加

链接https://leetcode.cn/leetbook/read/top-interview-questions-medium/xvw73v/

题目:给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
两数相加

解题思路: 使用两个指针,开始的时候分别指向两个链表的头节点。用他们相加和的个位数构造一个新的节点(因为一个节点只能存储一位数字),这两个指针每次运算完之后都要同时往后移一步,然后还要使用一个变量carry表示是否有进位。两个指针只要有一个不为空,或者carry不为0就继续循环。

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
        """
        使用两个指针,开始的时候分别指向两个链表的头节点。用他们相加和的个位数构造一个新的节点(因为一个节点只能存储一位数字),这两个指针每次运算完之后都要同时往后移一步,然后还要使用一个变量carry表示是否有进位。两个指针只要有一个不为空,或者carry不为0就继续循环
        """
        dummyNode=ListNode() # 定义一个哑节点,指向新链表头结点
        preNode=dummyNode # 定义当前节点的前一个节点
        carry=0 # 定义进位变量

        while l1 or l2 or carry: #当l1不为空或l2不为空或carry不为零时
            sum=carry # 定义两数各位之和变量
            # 链表1求和操作
            if l1: 
                sum+=l1.val
                l1=l1.next
            # 链表2求和操作
            if l2: 
                sum+=l2.val
                l2=l2.next
            
            # 创建新节点,preNode的next指针指向新节点
            # 因为链表节点只能存储一个数字,对sum取余
            preNode.next=ListNode(sum%10) 
            
            carry=sum//10 # 进位变量
            preNode=preNode.next # preNode后移一位
        
        #返回dummyNode的next指针指向的值,dummyNode指向新链表头结点
        return dummyNode.next 

2.2 奇偶链表

链接https://leetcode.cn/leetbook/read/top-interview-questions-medium/xvdwtj/

题目:给定单链表的头节点 head ,将所有索引为奇数的节点和索引为偶数的节点分别组合在一起,然后返回重新排序的列表。
第一个节点的索引被认为是 奇数 , 第二个节点的索引为 偶数 ,以此类推。
请注意,偶数组和奇数组内部的相对顺序应该与输入时保持一致。
你必须在 O(1) 的额外空间复杂度和 O(n) 的时间复杂度下解决这个问题。
奇偶链表

解题思路:把奇数节点串到一起,我们称之为奇数链表,偶数节点串到一起,我们称之为偶数链表。最后再把偶数链表挂到奇数链表的后面即可。

在这里插入图片描述

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def oddEvenList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        # 链表长度小于等于1则返回原链表
        if head==None or head.next==None:
            return head
        
        oddHead=head # 奇数链表头节点
        oddCur=oddHead # 奇数链表当前节点(指针)
        evenHead=head.next # 偶数链表头节点
        evenCur=evenHead # 偶数链表当前节点(指针)

        while evenCur and evenCur.next: # 当链表未被遍历完
            oddCur.next=oddCur.next.next # 奇数节点串一起
            evenCur.next=evenCur.next.next #偶数节点串一起
            # 奇偶指针后移一位
            oddCur=oddCur.next
            evenCur=evenCur.next

        # 奇数链表与偶数链表合并,奇数链表末尾指向偶数链表头部
        oddCur.next=evenHead
        return oddHead

2.3 相交链表

链接https://leetcode.cn/leetbook/read/top-interview-questions-medium/xv02ut/

题目:给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
相交链表
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。

解题思路:题目意思是判断两个链表是否相交,如果相交就返回他们的相交的交点,如果不相交就返回null。链表相交指的是指针指向同一位置。

  • 思路1:先把第一个链表的节点全部存放到集合set中,然后遍历第二个链表的每一个节点,判断在集合set中是否存在,如果存在就直接返回这个存在的结点。如果遍历完了,在集合set中还没找到,说明他们没有相交
  • 思路2:先统计两个链表的长度,如果两个链表的长度不一样,就让链表长的先走,直到两个链表长度一样,这个时候两个链表再同时每次往后移一步,看节点是否一样,如果有相等的,说明这个相等的节点就是两链表的交点,否则如果走完了还没有找到相等的节点,说明他们没有交点,直接返回null即可。以下实现代码采用思路二
  • 思路3:双指针见题目链接
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
        '''先统计两个链表的长度,如果两个链表的长度不一样,就让链表长的先走,直到两个链表长度一样,这个时候两个链表再同时每次往后移一步,看节点是否一样,如果有相等的,说明这个相等的节点就是两链表的交点,否则如果走完了还没有找到相等的节点,说明他们没有交点,直接返回null即可'''
        listA=list()
        listB=list()

        # 统计链表长度
        ahead=headA
        bhead=headB # 定义一个新的链表用于统计原链表长度,否则原链表走到末端后为None
        while ahead:
            listA.append(ahead.val)
            ahead=ahead.next
        while bhead:
            listB.append(bhead.val)
            bhead=bhead.next
        lenA=len(listA)
        lenB=len(listB)

        # 较长的链表先走几步直到两链表等长
        while lenA!=lenB:
            if lenA>lenB:
                headA=headA.next
                lenA-=1
            else:
                headB=headB.next
                lenB-=1

        # 两链表等长后开始比较,走到最后若不相交则headA为空,若相交则跳出循环,headA为交点
        while headA!=headB:
            headA=headA.next
            headB=headB.next
        
        return headA
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值