链表相关知识及练习

1.1链表基础理论

1.1.1 链表的分类

  1. 单链表(Singly Linked List):每个节点包含一个数据元素和一个指向下一个节点的指针。
  2. 双向链表(Doubly Linked List):每个节点包含一个数据元素和两个指针,一个指向前一个节点,一个指向下一个节点。
  3. 循环链表(Circular Linked List):链表的最后一个节点的指针指向链表的第一个节点,形成一个闭环。
  4. 双向循环链表:结合了双向链表和循环链表的特点。

1.1.2链表的特点 

  1. 动态大小:链表的大小可以在运行时动态变化,不需要预先分配固定大小的存储空间。
  2. 内存非连续:链表的节点在内存中不必连续存储,每个节点的内存地址可以是任意的。
  3. 访问效率:访问链表中的元素需要从头节点开始遍历,因此访问特定元素的时间复杂度为O(n)。
  4. 插入和删除效率高:在链表中插入或删除元素只需要改变指针,不需要移动其他元素,因此这些操作的时间复杂度为O(1),前提是已知插入或删除位置的前一个节点。

2.1 移除链表中的元素

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

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

2.1.2移除头节点元素

如果previousNone,表示要移除的是头节点,所以直接更新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

3.1设计链表

3.1.1获取第n个节点的值

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

一般步骤:

  1. 初始化一个指针指向链表的头节点。(虚拟指针)
  2. 遍历链表,每次移动指针到下一个节点。
  3. 计数遍历过程中经过的节点。
  4. 当计数器达到n时,当前指针指向的节点就是第n个节点。
  5. 返回该节点的值。

如果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)。

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

  1. 创建一个新的节点,将给定的值赋给这个节点。
  2. 将新节点的next指针指向当前的头节点。
  3. 更新链表的头节点指针,使其指向新节点。
def addAtHead(self, val):
        self.dummy_head.next = ListNode(val, self.dummy_head.next)
        self.size += 1

3.1.3尾部插入节点 

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

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

  1. 创建一个新的节点,将给定的值赋给这个节点。
  2. 如果链表为空(头节点为None),则新节点成为头节点。
  3. 如果链表不为空,遍历链表直到最后一个节点。
  4. 将最后一个节点的next指针指向新节点。
 def addAtTail(self, val):
        current = self.dummy_head
        while current.next:
            current = current.next
        current.next = ListNode(val)
        self.size += 1

3.1.4在第n个节点前插入

在链表的第n个节点前插入一个新节点是一个常见的操作,它涉及找到第n个节点,然后在这个节点前插入新节点。这个操作的时间复杂度为O(n)【因为最坏情况下需要遍历整个链表】

在链表的第n个节点前插入新节点的步骤:

  1. 创建一个新的节点,将给定的值赋给这个节点。
  2. 如果n为0,表示在头部插入新节点,将新节点的next指针指向当前的头节点,然后更新头节点指针。
  3. 如果n大于0且小于链表长度,找到第n-1个节点,将新节点插入到这个节点和第n个节点之间。
  4. 如果n大于链表长度,表示在尾部插入新节点。
def addAtIndex(self,index,val):
       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

3.1.5删除第n个节点

删除链表中的第n个节点是一个常见的操作,它涉及找到第n-1个节点,然后调整其next指针,使其跳过第n个节点,直接指向第n+1个节点。

删除链表中第n个节点的步骤:

  1. 如果n为0,表示删除头节点,更新头节点指针为头节点的下一个节点。
  2. 如果n大于0,找到第n-1个节点。
  3. 如果找到的第n-1个节点存在,将它的next指针指向第n个节点的下一个节点,从而删除第n个节点。
  4. 如果n大于链表长度,不执行任何操作。
def deleteAtIndex(self, index):
        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

4.1递归概述

4.1.1递归基本理论

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

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

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

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

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

4.1.2反转链表 

迭代法

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

  1. 初始化三个指针:prev(指向前一个节点,初始为None)、curr(当前节点,初始为链表的头节点)、next(用于临时存储下一个节点)。
  2. 遍历链表,直到currNone(即到达链表末尾)。
  3. 在每次迭代中,先保存curr.nextnext,然后改变curr.next指向prev,接着将prevcurr向前移动一位。
  4. 最后,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  

递归法 

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

  1. 定义一个递归函数,接收当前节点head作为参数。
  2. 基本情况:如果headNonehead.nextNone,则直接返回head(即空链表或只有一个节点的链表不需要反转)。
  3. 递归步骤:递归调用反转head.next之后的链表,然后逐个节点将它们接到当前节点head的后面。
  4. 最后,将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 两两交换链表中的节点

迭代法

  1. 首先检查链表是否为空或者只有一个节点,如果是,则不需要交换,直接返回原链表。
  2. 初始化一个哑节点(dummy node),它的next指向头节点,这样可以简化边界条件的处理。
  3. 使用两个指针prevcurrprev指向哑节点,curr指向当前需要交换的节点对的第一个节点。
  4. 遍历链表,每次交换currcurr.next节点,然后将prev指向新的节点对。
  5. 重复上述步骤,直到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

递归法 

  1. 定义一个递归函数,接收当前节点head作为参数。
  2. 基本情况:如果head为空或head.next为空,则直接返回head
  3. 递归步骤:首先递归地交换head.next.next之后的链表,然后交换headhead.next
  4. 最后,将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)

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

  1. get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
  2. addAtHead(val):在链表的头部添加一个值为 val 的节点。
  3. addAtTail(val):在链表的尾部添加一个值为 val 的节点。
  4. addAtIndex(index, val):如果索引 index 已存在,则将值为 val 的节点插入到该索引处的前面;如果索引 index 大于链表长度,则将 val 添加到链表的尾部;如果 index 小于0,则返回。
  5. deleteAtIndex(index):如果索引 index 有效,则删除链表中索引为 index 的节点。

 

“两数相加”(Add Two Numbers)

题目要求给定两个非负整数的反向链表表示形式,数字以链表中的节点序列的逆序存储,返回它们的和的链表表示形式。

206 

“Reverse Linked List”(反转链表)

要求你定义一个函数,实现链表的反转,并返回反转后的链表的头节点。(递归法和迭代法均可使用,以下是递归法演示)

24 

“Swap Nodes in Pairs”(两两交换链表中的节点)

要求你编写一个函数,实现两两交换链表中的节点,并返回交换后的链表的头节点。

迭代法) 

递归法来解决本题(两两交换链表中的节点)【加分版】

递归的基本思想是将链表分成两部分,第一部分包含前两个节点,第二部分是剩余的链表。然后,递归地交换第二部分,最后将第一部分的两个节点与递归交换后的链表连接起来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值