链表知识与练习

1.1链表基础理论

1.1.1 链表的分类

单链表(Singly Linked List):每个节点包含一个数据元素和一个指向下一个节点的指针。

双向链表(Doubly Linked List):每个节点包含一个数据元素和两个指针,一个指向前一个节点,一个指向下一个节点。

循环链表(Circular Linked List):链表的最后一个节点的指针指向链表的第一个节点,形成一个闭环。

双向循环链表:结合了双向链表和循环链表的特点。

1.1.2链表的特点 

动态大小:链表的大小可以在运行时动态变化,不需要预先分配固定大小的存储空间。

内存非连续:链表的节点在内存中不必连续存储,每个节点的内存地址可以是任意的。

访问效率:访问链表中的元素需要从头节点开始遍历,因此访问特定元素的时间复杂度为O(n)。

插入和删除效率高:在链表中插入或删除元素只需要改变指针,不需要移动其他元素,因此这些操作的时间复杂度为O(1),前提是已知插入或删除位置的前一个节点。

 

2.1 移除链表中的元素

 

2.1.1移除(除头节点以外的)任意元素

 

LinkedList类有一个remove方法,它接受一个值作为参数,并从链表中移除所有值为该参数的节点。用这个方法遍历链表,使用两个指针current和previous来跟踪当前节点和前一个节点。如果当前节点的值等于要移除的值,它会更新previous.next指针来跳过当前节点。如果当前节点的值不等于要移除的值,它会将previous更新为current,然后继续遍历链表。

 

2.1.2移除头节点元素

 

如果previous是None,表示要移除的是头节点,所以直接更新self.head。

 

# 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, val):

 

"""

 

:type head: ListNode

 

:type val: int

 

:rtype: ListNode

 

"""

 

# 创建一个虚拟头部节点,它的 next 指向头节点

 

dummy = ListNode(0)

 

dummy.next = head

 

current = dummy

 

while current.next:

 

if current.next.val == val:

 

current.next = current.next.next # 移除当前节点

 

else:

 

current = current.next # 移动到下一个节点

 

return dummy.next # 返回虚拟头部节点的下一个节点,即新链表的头节点

 

# 示例

 

def print_list(node):

 

while node:

 

print(node.val, end=" -> ")

 

node = node.next

 

print("None")

 

# 创建链表 1 -> 2 -> 6 -> 3 -> 4 -> 5 -> 6

 

head = ListNode(1)

 

head.next = ListNode(2)

 

head.next.next = ListNode(6)

 

head.next.next.next = ListNode(3)

 

head.next.next.next.next = ListNode(4)

 

head.next.next.next.next.next = ListNode(5)

 

head.next.next.next.next.next.next = ListNode(6)

 

# 移除值为 6 的元素

 

sol = Solution()

 

new_head = sol.removeElements(head, 6)

 

# 输出新的链表

 

print_list(new_head) # 输出: 1 -> 2 -> 3 -> 4 -> 5 -> None

# 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, val):

        """

        :type head: ListNode

        :type val: int

        :rtype: ListNode

        """

        # 创建一个虚拟头部节点,它的 next 指向头节点

        dummy = ListNode(0)

        dummy.next = head

        current = dummy

 

        while current.next:

            if current.next.val == val:

                current.next = current.next.next # 移除当前节点

            else:

                current = current.next # 移动到下一个节点

 

        return dummy.next # 返回虚拟头部节点的下一个节点,即新链表的头节点

 

# 示例

def print_list(node):

    while node:

        print(node.val, end=" -> ")

        node = node.next

    print("None")

 

# 创建链表 1 -> 2 -> 6 -> 3 -> 4 -> 5 -> 6

head = ListNode(1)

head.next = ListNode(2)

head.next.next = ListNode(6)

head.next.next.next = ListNode(3)

head.next.next.next.next = ListNode(4)

head.next.next.next.next.next = ListNode(5)

head.next.next.next.next.next.next = ListNode(6)

 

# 移除值为 6 的元素

sol = Solution()

new_head = sol.removeElements(head, 6)

 

# 输出新的链表

print_list(new_head) # 输出: 1 -> 2 -> 3 -> 4 -> 5 -> None

3.1设计链表

 

3.1.1获取第n个节点的值

 

获取链表中第n个节点的值通常需要从头节点开始遍历链表,直到到达第n个节点。

 

一般步骤:

 

初始化一个指针指向链表的头节点。(虚拟指针)

 

遍历链表,每次移动指针到下一个节点。

 

计数遍历过程中经过的节点。

 

当计数器达到n时,当前指针指向的节点就是第n个节点。

 

返回该节点的值。

 

如果n超出了链表的范围,通常返回-1或者None表示没有找到。

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):

        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

3.1.2头部插入节点

 

在链表的头部插入节点是一种常见的操作,它通常涉及更新头节点的指针,使其指向新插入的节点。新节点的指针则指向原来的头节点。整个过程不需要遍历整个链表,因此插入操作的时间复杂度为O(1)。

 

单链表头部插入节点的步骤:

 

创建一个新的节点,将给定的值赋给这个节点。

 

将新节点的next指针指向当前的头节点。

 

更新链表的头节点指针,使其指向新节点。

def addAtHead(self, val):

        self.dummy_head.next = ListNode(val, self.dummy_head.next)

        self.size += 1

3.1.3尾部插入节点 

 

在链表的尾部插入节点是另一种常见的操作,它涉及遍历整个链表直到最后一个节点,然后更新最后一个节点的指针,使其指向新插入的节点。如果链表为空,新节点同时成为头节点。这个过程的时间复杂度为O(n)【因为最坏情况下需要遍历整个链表】

 

单链表尾部插入节点的步骤:

 

创建一个新的节点,将给定的值赋给这个节点。

 

如果链表为空(头节点为None),则新节点成为头节点。

 

如果链表不为空,遍历链表直到最后一个节点。

 

将最后一个节点的next指针指向新节点。

 def addAtTail(self, val):

        current = self.dummy_head

        while current.next:

            current = current.next

        current.next = ListNode(val)

        self.size += 1

4.1递归概述

 

4.1.1递归基本理论

 

递归:a)在定义一个过程或函数时出现调用本过程或本函数的成分,称之为递归。

 

           b)若调用自身,称之为直接递归。若过程或函数p调用过程或函数q,而q又调用p,称之为间接递归。 如果一个递归过程或递归函数中递归调用语句是最后一条执行语句,则称这种递归调用为尾递归。

 

递归允许一个函数调用自身来解决问题。递归通常用于解决那些可以分解为多个小而相似问题的情况。递归方法通常包括两个主要部分:

 

基本情况(Base Case):这是递归停止的条件。它定义了问题最简单的形式,可以直接解决而不需要进一步的递归调用。

 

递归步骤(Recursive Step):这是函数调用自身来解决问题的一部分。在每次递归调用中,问题都会向基本情况靠近一步。

4.1.2反转链表 

 

迭代法

 

迭代法的基本思路是遍历链表,逐个节点地将其指向前一个节点。

 

初始化三个指针:prev(指向前一个节点,初始为None)、curr(当前节点,初始为链表的头节点)、next(用于临时存储下一个节点)。

 

遍历链表,直到curr为None(即到达链表末尾)。

 

在每次迭代中,先保存curr.next到next,然后改变curr.next指向prev,接着将prev和curr向前移动一位。

 

最后,prev将指向新的头节点

class ListNode:

    def __init__(self, val=0, next=None):

        self.val = val

        self.next = next

 

def reverseList(head):

    prev = None

    curr = head

    while curr:

        next = curr.next  

        curr.next = prev  

        prev = curr       

        curr = next

    return prev  

递归法 

 

递归法的基本思路是将链表的后一部分先反转,然后逐个节点地将当前节点接到反转后的链表上。

 

定义一个递归函数,接收当前节点head作为参数。

 

基本情况:如果head为None或head.next为None,则直接返回head(即空链表或只有一个节点的链表不需要反转)。

 

递归步骤:递归调用反转head.next之后的链表,然后逐个节点将它们接到当前节点head的后面。

 

最后,将head.next设置为None,表示这是新的尾节点。

class ListNode:

    def __init__(self, val=0, next=None):

        self.val = val

        self.next = next

 

def reverseList(head):

    if not head or not head.next:

        return head

    new_head = reverseList(head.next)  

    head.next.next = head  

    head.next = None      

    return new_head       

4.1.3 两两交换链表中的节点

 

迭代法

 

首先检查链表是否为空或者只有一个节点,如果是,则不需要交换,直接返回原链表。

 

初始化一个哑节点(dummy node),它的next指向头节点,这样可以简化边界条件的处理。

 

使用两个指针prev和curr,prev指向哑节点,curr指向当前需要交换的节点对的第一个节点。

 

遍历链表,每次交换curr和curr.next节点,然后将prev指向新的节点对。

 

重复上述步骤,直到curr为空或curr.next为空。

class ListNode:

    def __init__(self, val=0, next=None):

        self.val = val

        self.next = next

 

def swapPairs(head):

    if not head or not head.next:

        return head

 

    dummy = ListNode(0)

    dummy.next = head

    prev = dummy

 

    while prev.next and prev.next.next:

        first = prev.next

        second = prev.next.next

        first.next = second.next

        second.next = first

        prev.next = second

        prev = first

 

    return dummy.next

递归法 

 

定义一个递归函数,接收当前节点head作为参数。

 

基本情况:如果head为空或head.next为空,则直接返回head。

 

递归步骤:首先递归地交换head.next.next之后的链表,然后交换head和head.next。

 

最后,将head.next.next指向head,以确保链表的连续性。

class ListNode:

    def __init__(self, val=0, next=None):

        self.val = val

        self.next = next

 

def swapPairs(head):

    if not head or not head.next:

        return head

 

    first = head

    second = head.next

    first.next = swapPairs(second.next)

    second.next = first

 

    return second

5.1练习(LeetCode)

 

707 

 

“设计链表”(Design Linked List)

 

题目要求设计一个链表结构,支持以下操作:

 

get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。

 

addAtHead(val):在链表的头部添加一个值为 val 的节点。

 

addAtTail(val):在链表的尾部添加一个值为 val 的节点。

 

addAtIndex(index, val):如果索引 index 已存在,则将值为 val 的节点插入到该索引处的前面;如果索引 index 大于链表长度,则将 val 添加到链表的尾部;如果 index 小于0,则返回。

 

deleteAtIndex(index):如果索引 index 有效,则删除链表中索引为 index 的节点。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

2301_80658062

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值