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 的节点。