第二章 链表

第二章 链表

1. 基础知识

链表是⼀种通过指针串联在⼀起的线性结构,每⼀个节点由两部分组成,⼀个是数据域⼀个是指针域(存放指向下⼀个节点的指针),最后⼀个节点的指针域指向null(空指针的意思)。
链表的⼊⼝节点称为链表的头结点也就是####head

类型

单链表

请添加图片描述

单链表中的指针域只能指向节点的下⼀个节点。

双链表

每⼀个节点有两个指针域,⼀个指向下⼀个节点,⼀个指向上⼀个节点。
请添加图片描述

既可以向前查询也可以向后查询

循环链表

链表⾸尾相连。
请添加图片描述

循环链表可以⽤来解决约瑟夫环问题。

链表的存储⽅式

数组是在内存中是连续分布的,但是链表在内存中可不是连续分布的。
链表是通过指针域的指针链接在内存中各个节点。
请添加图片描述

这个链表起始节点为2, 终⽌节点为7, 各个节点分布在内存的不同地址空间上,通过指针串联在⼀起。

链表的定义

class ListNode:
    def __init__(self, val, next=None):
        self.val = val
        self.next = next

链表的操作

删除节点

请添加图片描述

删除D节点,只要将C节点的next指针 指向E节点就可以了。

添加节点

请添加图片描述

以看出链表的增添和删除都是O(1)操作,也不会影响到其他节点。
但是要注意,要是删除第五个节点,需要从头节点查找到第四个节点通过next指针进⾏删除操作,查找的时间复杂
度是O(n)。

链表和数组性能分析

请添加图片描述

2. 移除链表元素

力扣题目链接
手把手带你学会操作链表 | LeetCode:203.移除链表元素

思路

请添加图片描述

因为单链表的特殊性,只能指向下⼀个节点,刚刚删除的是链表的中第⼆个,和第四个节点,那么如果删除的是头结点⼜该怎么办呢?

原链表删除元素

请添加图片描述

只要将头结点向后移动⼀位就可以,这样就从链表中移除了⼀个头结点

  • 时间复杂度: O(n)
  • 空间复杂度: O(1)
虚拟头结点

按照统⼀的⽅式进⾏移除
请添加图片描述

# 虚拟头节点法
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
        # 创建虚拟头部节点以简化删除过程
        dummy_head = ListNode(next = head)
        
        # 遍历列表并删除值为val的节点
        current = dummy_head
        while current.next:
            if current.next.val == val:
                current.next = current.next.next
            else:
                current = current.next
        
        return dummy_head.next
  • 时间复杂度: O(n)
  • 空间复杂度: O(1)

3. 设计链表

力扣题目链接

帮你把链表操作学个通透!LeetCode:707.设计链表

思路

设置虚拟头节点

class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next
        
class MyLinkedList:
    def __init__(self):
        self.dummy_head = ListNode()
        self.size = 0

    def get(self, index: int) -> int:
        if index < 0 or index >= self.size:
            return -1
        
        current = self.dummy_head.next
        for i in range(index):
            current = current.next
            
        return current.val

    def addAtHead(self, val: int) -> None:
        self.dummy_head.next = ListNode(val, self.dummy_head.next)
        self.size += 1

    def addAtTail(self, val: int) -> None:
        current = self.dummy_head
        while current.next:
            current = current.next
        current.next = ListNode(val)
        self.size += 1

    def addAtIndex(self, index: int, val: int) -> None:
        if index < 0 or index > self.size:
            return
        
        current = self.dummy_head
        for i in range(index):
            current = current.next
        current.next = ListNode(val, current.next)
        self.size += 1

    def deleteAtIndex(self, index: int) -> None:
        if index < 0 or index >= self.size:
            return
        
        current = self.dummy_head
        for i in range(index):
            current = current.next
        current.next = current.next.next
        self.size -= 1


# Your MyLinkedList object will be instantiated and called as such:
# obj = MyLinkedList()
# param_1 = obj.get(index)
# obj.addAtHead(val)
# obj.addAtTail(val)
# obj.addAtIndex(index,val)
# obj.deleteAtIndex(index)

4. 翻转链表

力扣题目链接
帮你拿下反转链表 | LeetCode:206.反转链表 | 双指针法 | 递归法

思路

只需要改变链表的next指针的指向,直接将链表反转 ,⽽不⽤重新定义⼀个新的链表
请添加图片描述

双指针法

⾸先定义⼀个cur指针,指向头结点,再定义⼀个pre指针,初始化为null。
然后就要开始反转了,⾸先要把 cur->next 节点⽤tmp指针保存⼀下,也就是保存⼀下这个节点。
为什么要保存⼀下这个节点呢,因为接下来要改变 cur->next 的指向了,将cur->next 指向pre ,此时已经反转了第⼀个节点了。
接下来,就是循环⾛如下代码逻辑了,继续移动pre和cur指针。
最后, cur 指针已经指向了null,循环结束,链表也反转完毕了。 此时我们return pre指针就可以了, pre指针就指向了新的头结点。

(版本一)双指针法
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        cur = head   
        pre = None
        while cur:
            temp = cur.next # 保存一下 cur的下一个节点,因为接下来要改变cur->next
            cur.next = pre #反转
            #更新pre、cur指针
            pre = cur
            cur = temp
        return pre
递归法
(版本二)递归法
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        return self.reverse(head, None)
    def reverse(self, cur: ListNode, pre: ListNode) -> ListNode:
        if cur == None:
            return pre
        temp = cur.next
        cur.next = pre
        return self.reverse(temp, cur)

5. 两两交换链表中的节点

力扣题目链接

帮你把链表细节学清楚! | LeetCode:24. 两两交换链表中的节点

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next

class Solution:
    def swapPairs(self, head: ListNode) -> ListNode:
        dummy_head = ListNode(next=head)
        current = dummy_head
        
        # 必须有cur的下一个和下下个才能交换,否则说明已经交换结束了
        while current.next and current.next.next:
            temp = current.next # 防止节点修改
            temp1 = current.next.next.next
            
            current.next = current.next.next
            current.next.next = temp
            temp.next = temp1
            current = current.next.next
        return dummy_head.next

还可以用递归的方式self.swapPairs()

# 递归版本
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next

class Solution:
    def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if head is None or head.next is None:
            return head

        # 待翻转的两个node分别是pre和cur
        pre = head
        cur = head.next
        next = head.next.next
        
        cur.next = pre  # 交换
        pre.next = self.swapPairs(next) # 将以next为head的后续链表两两交换
         
        return cur

6. 删除链表的倒数第N个节点

力扣题目链接

链表遍历学清楚! | LeetCode:19.删除链表倒数第N个节点

思路

双指针的经典应⽤,如果要删除倒数第n个节点,让fast先移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next

class Solution:
    def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
        # 创建一个虚拟节点,并将其下一个指针设置为链表的头部
        dummy_head = ListNode(0, head)
        
        # 创建两个指针,慢指针和快指针,并将它们初始化为虚拟节点
        slow = fast = dummy_head
        
        # 快指针比慢指针快 n+1 步
        for i in range(n+1):
            fast = fast.next
        
        # 移动两个指针,直到快速指针到达链表的末尾
        while fast:
            slow = slow.next
            fast = fast.next
        
        # 通过更新第 (n-1) 个节点的 next 指针删除第 n 个节点
        slow.next = slow.next.next
        
        return dummy_head.next
  • 时间复杂度: O(n)
  • 空间复杂度: O(1)

7. ⾯试题 02.07. 链表相交

力扣题目链接

思路

求两个链表交点节点的指针。 这⾥同学们要注意,交点不是数值相等,⽽是指针相等
请添加图片描述

求出两个链表的⻓度,并求出两个链表⻓度的差值,然后让curA移动到,和curB 末尾对⻬的位置
请添加图片描述

依次比较指针是否相同

class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        lenA, lenB = 0, 0
        cur = headA
        while cur:         # 求链表A的长度
            cur = cur.next 
            lenA += 1
        cur = headB 
        while cur:         # 求链表B的长度
            cur = cur.next 
            lenB += 1
        curA, curB = headA, headB
        if lenA > lenB:     # 让curB为最长链表的头,lenB为其长度
            curA, curB = curB, curA
            lenA, lenB = lenB, lenA 
        for _ in range(lenB - lenA):  # 让curA和curB在同一起点上(末尾位置对齐)
            curB = curB.next 
        while curA:         #  遍历curA 和 curB,遇到相同则直接返回
            if curA == curB:
                return curA
            else:
                curA = curA.next 
                curB = curB.next
        return None 

8. 环形链表II

力扣题目链接
把环形链表讲清楚! 如何判断环形链表?如何找到环形链表的入口? LeetCode:142.环形链表II

思路

快慢指针法,分别定义 fast 和 slow 指针,从头结点出发, fast指针每次移动两个节点, slow指针每次移动⼀个节点,如果 fast 和 slow指针在途中相遇 ,说明这个链表有环
当两个指针进入环后,相当于fast在以一格的速度追slow

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None


class Solution:
    def detectCycle(self, head: ListNode) -> ListNode:
        slow = head
        fast = head
        
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
            
            # If there is a cycle, the slow and fast pointers will eventually meet
            if slow == fast:
                # Move one of the pointers back to the start of the list
                slow = head
                while slow != fast:
                    slow = slow.next
                    fast = fast.next
                return slow
        # If there is no cycle, return None
        return None

找环入口

请添加图片描述

请添加图片描述

相遇节点就是入口

总结:链表基础与操作

链表是一种通过指针连接的线性数据结构,每个节点包含数据域和指针域。根据指针的数量和连接方式,链表有多种类型,如单链表、双链表和循环链表。

基础知识

  1. 节点结构:每个节点由数据和指针组成,指向下一个节点,最后一个节点指向 null
  2. 链表类型
    • 单链表:每个节点有一个指针指向下一个节点。
    • 双链表:每个节点有两个指针,分别指向前一个和后一个节点。
    • 循环链表:最后一个节点指向头节点,形成一个环。

链表的操作

  1. 添加节点:在链表的头部、尾部或指定位置添加新节点,时间复杂度为 O(1)。
  2. 删除节点:可以通过更新指针实现,时间复杂度为 O(n)(查找节点时)。

链表常见问题

  1. 移除链表元素力扣题目链接

    • 使用虚拟头结点简化删除操作,保证时间和空间复杂度均为 O(n) 和 O(1)。
  2. 设计链表力扣题目链接

    • 实现基本的链表操作,如获取、添加、删除节点等。
  3. 反转链表力扣题目链接

    • 使用双指针法或递归法对链表进行反转。
  4. 删除链表倒数第 N 个节点力扣题目链接

    • 采用双指针法,先移动快指针 n 步,再一起移动,找到并删除目标节点。
  5. 链表相交力扣题目链接

    • 通过比较两链表的长度,调整起始位置,然后逐步查找相交节点。
  6. 环形链表力扣题目链接

    • 使用快慢指针法判断链表是否有环,并找到环的入口。

总结

掌握链表的基本概念及其操作是数据结构中的重要环节,通过实际问题的练习,能够提升对链表结构的理解和应用能力。通过各种算法题的练习,对链表的操作和应用可以更加游刃有余。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值