val
(值)和next
(指向下一个节点的指针)。我们需要通过修改这些指针来实现交换。head
和下一个节点 head.next
存在,我们要交换这两个节点,交换后的顺序是:head.next
指向 head
,head
指向 head.next.next
。head.next.next。
为了方便处理链表头节点的交换,我们可以使用一个虚拟节点(dummy
),它指向链表的头部,这样可以避免对链表头节点的特殊处理。# 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]:
dummy_head = ListNode(next = head) #初始化虚拟头节点指向原链表头节点
cur = dummy_head #从虚拟头节点开始操作
while cur.next and cur.next.next: #当cur的下一个和下下个不为空才进行交换操作
temp = cur.next #暂存两两交换的前一个元素,例如“1”
temp1 = cur.next.next.next #暂存"3"的地址,因为“2”指向“1”后,不暂存“3”的地址
#会丢失
cur.next = cur.next.next #虚拟头节点指向"2"
cur.next.next = temp #"2"指向"1"
temp.next = temp1 #"1"指向“3”
cur = cur.next.next #cur指向原链表的第2个元素位置,因为要操作第三、四元
#素了,理解为隔一个遍历
#理解为隔一个遍历
return dummy_head.next
# 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: Optional[ListNode], n: int) -> Optional[ListNode]:
dummy_head = ListNode(0, head) #初始化虚拟节点指向头节点
left, right = dummy_head, dummy_head #初始化左右指针
for i in range(n+1):
right = right.next #创建长度为N+1的滑动窗口
while right: #直到右指针到达链表尾
left = left .next #左指针同步移动
right = right.next #右指针同步移动
left.next = left.next.next #此时left指向倒数N+1个节点,通过left删除倒数第N个节点
return dummy_head.next
None
。假设链表A:
4 | 1 | 8 | 4 | 5 |
链表B:
5 | 0 | 1 | 8 | 4 | 5 |
指针A遍历完链表A后,指向链表B的头部,遍历顺序为:
4 | 1 | 8 | 4 | 5 | 5 | 0 | 1 | 8 | 4 | 5 |
指针B遍历完链表A后,指向链表A的头部,遍历顺序为:
5 | 0 | 1 | 8 | 4 | 5 | 4 | 1 | 8 | 4 | 5 |
两个链表有交点时,指针最终会相遇,相遇节点为:
4 | 1 | 8 | 4 | 5 | 5 | 0 | 1 | 8 | 4 | 5 |
5 | 0 | 1 | 8 | 4 | 5 | 4 | 1 | 8 | 4 | 5 |
这里的关键点是当指针遍历到链表的末尾时,将其重新指向另一个链表的头部。这样做的目的是:
保证两个指针走的总距离相同,从而可以在相交的节点相遇。
# 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) -> ListNode:
if not headA or not headB: #处理边缘情况
return None
ptrA, ptrB = headA, headB
while ptrA != ptrB: #遍历2个链表直到相交
ptrA = ptrA.next if ptrA else headB #遍历完A就接着遍历B
ptrB = ptrB.next if ptrB else headA #遍历完B就接着遍历A
return ptrA #返回交点,此处也可返回ptrB
slow
(慢指针)和 fast
(快指针)。快指针每次走两步,而慢指针每次走一步。假设链表中有环,快慢指针必定会在环中相遇。因为快指针每次比慢指针多走1步,假设存在环,快指针先进入环内,每次以1步的相对速度追赶慢指针,必定能追上。找环的起点:将 slow
指针重新放回链表的头节点,并保持 fast
指针不变。然后,让 slow
和 fast
每次都移动一步。当 slow
和 fast
再次相遇时,位置就是环的入口。
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
# 快慢指针初始化
slow, fast = head, head
# Step 1: 检测链表是否有环
while fast and fast.next:
slow = slow.next # 慢指针每次走一步
fast = fast.next.next # 快指针每次走两步
# 如果快慢指针相遇,说明有环
if slow == fast:
# Step 2: 找环的入口
slow = head # 将slow重新指向链表的头部
while slow != fast:
slow = slow.next
fast = fast.next
return slow # 相遇的节点即为环的起始节点
return None # 如果没有环,返回None
思路2:通过哈希表(集合)来判断链表是否存在环,当一个节点重复出现时,就说明存在环,并返回该节点作为环的入口。如果链表没有环,最终 head
会变为 None
,跳出循环并返回 None
,表示没有环。
时间复杂度是 O(n),空间复杂度是 O(n),适用于链表的大小可以接受的场景。
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
# 创建一个集合来存储已访问过的节点
visited = set()
# 遍历链表
while head:
# 如果当前节点已经被访问过,说明链表中有环,返回该节点
if head in visited:
return head
# 将当前节点加入到已访问节点集合中
visited.add(head)
# 移动到下一个节点
head = head.next
# 如果遍历完整个链表后没有发现环,返回 None
return None
使用了额外的集合来存储已访问的节点,这导致了较高的空间复杂度 O(n)。
如果链表特别长且环很大,可能会占用较多的内存。如果空间复杂度不是特别敏感,使用这个方法来检测链表环是非常直接和高效的。