代码随想录第四天:(交换节点、删除节点、链表相交、环形链表)

一、两两交换链表中的节点(Leetcode 24)

代码随想录第四天:(交换节点、删除节点、链表相交、环形链表)_第1张图片

思路:

  1. 链表节点的结构:每个节点都有两个部分:val(值)和next(指向下一个节点的指针)。我们需要通过修改这些指针来实现交换。
  2. 交换相邻节点:如果当前节点 head 和下一个节点 head.next 存在,我们要交换这两个节点,交换后的顺序是:head.next 指向 headhead 指向 head.next.next
  3. 遍历链表:每次交换后,我们跳过已经交换过的节点对,即跳到 head.next.next。为了方便处理链表头节点的交换,我们可以使用一个虚拟节点(dummy),它指向链表的头部,这样可以避免对链表头节点的特殊处理。
  4. 特殊情况处理:如果链表为空或只有一个节点,直接返回原链表。如果链表有两个节点,直接交换。

# 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

二、删除链表的倒数第N个节点(Leetcode 19)

代码随想录第四天:(交换节点、删除节点、链表相交、环形链表)_第2张图片

思路:因为单向链表只能从前往后遍历,要删除倒数第N个节点,我们可以设计一个长度为N+1的双指针滑动窗口,当右指针遍历到尾节点,左指针刚好指向倒数第N+1,这时候通过左指针删除倒数第N个元素。要删除倒数第N个节点,必须通过倒数第N+1个节点来操作

# 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

三、链表相交(Leetcode 0207)

代码随想录第四天:(交换节点、删除节点、链表相交、环形链表)_第3张图片

思路:通过两个指针分别从两个链表的头部开始遍历。如果指针A遍历完链表A后,指向链表B的头部,指针B遍历完链表B后,指向链表A的头部。每个指针都遍历过两个链表。如果有交点,指针最终会在交点相遇;如果没有交点,最终都会指向 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

四、环形链表(Leetcode 142)

代码随想录第四天:(交换节点、删除节点、链表相交、环形链表)_第4张图片

思路1:我们定义两个指针,slow(慢指针)和 fast(快指针)。快指针每次走两步,而慢指针每次走一步。假设链表中有环,快慢指针必定会在环中相遇。因为快指针每次比慢指针多走1步,假设存在环,快指针先进入环内,每次以1步的相对速度追赶慢指针,必定能追上。

找环的起点:slow 指针重新放回链表的头节点,并保持 fast 指针不变。然后,让 slowfast 每次都移动一步。当 slowfast 再次相遇时,位置就是环的入口。

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

如果链表特别长且环很大,可能会占用较多的内存。如果空间复杂度不是特别敏感,使用这个方法来检测链表环是非常直接和高效的。

你可能感兴趣的:(链表,数据结构)