您现在的位置是:首页 >学无止境 >代码随想录算法训练营第四天|LeetCode 24.两两交换链表中的节点、19.删除链表的倒数第N个节点、面试题02.07.链表相交、142.环形链表|| 网站首页学无止境

代码随想录算法训练营第四天|LeetCode 24.两两交换链表中的节点、19.删除链表的倒数第N个节点、面试题02.07.链表相交、142.环形链表||

Anastasia_sakura 2025-02-28 12:01:02
简介代码随想录算法训练营第四天|LeetCode 24.两两交换链表中的节点、19.删除链表的倒数第N个节点、面试题02.07.链表相交、142.环形链表||

24.两两交换链表中的节点

文档讲解:代码随想录

视频讲解:帮你把链表细节学清楚! | LeetCode:24. 两两交换链表中的节点_哔哩哔哩_bilibili

状态:之前做过一次,先尝试着想了一下,然后看了一下之前的递归解法。

递归法,没有用到虚拟头节点:

  • 时间复杂度:O(n)   n是链表的长度

  • 空间复杂度:O(n)   每一次递归调用都会在调用栈上占用一个空间,由于递归每次处理两个节点,因此递归调用的深度是链表长度的一半,即 n/2。因此,递归调用的栈空间占用是 O(n / 2),简化为 O(n)。

# 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]:
        if not head or not head.next:
            return head
        # 待翻转的两个node分别是one和two
        one = head
        two = one.next
        three = two.next

        two.next = one
        one.next = self.swapPairs(three) # 将以three为head的后续链表两两交换,one.next应该是下一组两两交换好的第一个节点

        return two 
        

用到虚拟头节点,迭代法:

  • 时间复杂度:O(n)
  • 空间复杂度:O(1) 
# 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]:
        # if not head or not head.next:
        #     return head   # 有虚拟头节点,就不用对头节点特殊处理了
        dummy_head = ListNode(next = head)
        cur = dummy_head # 知道要操作的两个节点前面的那个节点才能交换那两个节点?

        # 必须有cur的下一个和下下个才能交换,否则说明已经交换结束了
        while cur.next and cur.next.next: # 分别对应链表有偶数个和奇数个节点时;要先判断cur.next否则可能会引起空指针异常
            tmp = cur.next # 临时保存节点1
            tmp1 = cur.next.next.next # 临时保存节点3

            cur.next = cur.next.next # 交换顺序,dummy_head指向节点2
            cur.next.next = tmp # 节点2指向节点1
            tmp.next = tmp1 # 节点1指向节点3

            cur = cur.next.next # cur后移两位,准备下一轮交换

        return dummy_head.next
        

对比两种方法,可以对两种方法有更深的理解。

19.删除链表的倒数第N个节点

文档讲解:代码随想录

视频讲解:链表遍历学清楚! | LeetCode:19.删除链表倒数第N个节点_哔哩哔哩_bilibili

状态:发现去年也做过,有栈和双指针两个方法,看了一遍双指针的方法,然后默写对了

双指针:

关键在于如何用双指针找到要删除的节点

  • 时间复杂度:O(n)

  • 空间复杂度:O(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]:
        # 添加一个虚拟头节点,它的next指针指向链表的头节点,这样就不用添加关于头节点的特殊判断
        # 指针指向前驱节点时,好执行删除操作,就是用.next越过要删除的节点,指向后面的
        dummy = ListNode(0,head) 
        first = head #快指针
        second = dummy #慢指针
        # 快慢指针,first比second超前n个节点(快 n+1 步).这样当first到末尾时second恰好在要删除节点的前一个节点
        for i in range(n):
            first = first.next
        while first:
            first = first.next
            second = second.next
        second.next = second.next.next # 删除节点
        return dummy.next # 不能return head,因为head仍然指向已删除的那个节点

面试题02.07.链表相交

文档讲解:代码随想录

状态:和160.相交链表是一样的题目吧,发现已经做过两次了。有两种解法:哈希集合、双指针;

注意:数值相同,不代表指针相同。

# 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
        a = headA
        b = headB
        while a != b:
            if not a:
                a = headB
            else:
                a = a.next # 两个指针分别向后移动,当为空时就指向另一个链表,接着向后移动
            
            if not b:
                b = headA
            else:
                b = b.next
        return a  # 如果链表相交,两个指针最终走过的路程都是a_单独+ab_公共+b_单独的部分,是相等的,所以最终会同时指向交点;
# 如果链表不相交,两个指针同样会将两个链表走完然后都指向空即None;所以直接返回a即可
        

卡哥提供的思路是:相交链表如下图所示,求出两个链表的长度,并求出差值;将两个链表末尾位置对齐,curA指针指向短的链表头节点,另一个指针curB指向长的链表上与a指针对齐的节点;此时即可比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到交点,否则循环退出返回空指针。

# 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
        lenA = 0 # 求长度
        lenB = 0
        cur = headA
        while cur:
            cur = cur.next
            lenA += 1
        cur = headB
        while cur:
            cur = cur.next
            lenB += 1
        
        curA, curB = headA, headB
        if lenB < lenA:
            curA, curB = headB, headA
            lenA, lenB = lenB, lenA   # 让较长的链表当B,后面的情况就不用分类写了

        for _ in range(lenB - lenA):
            curB = curB.next   # 移动B指针,两个指针指向末尾对齐后,相同的起点位置
        while curA:
            if curA != curB:
                curA = curA.next
                curB = curB.next
            else:
                return curA  # curA == curB时,就是两个指针移动到了同一个位置即交点,直接返回
        return None   # 两个链表没有相交

142.环形链表||

文档讲解:代码随想录

视频讲解:把环形链表讲清楚! 如何判断环形链表?如何找到环形链表的入口? LeetCode:142.环形链表II_哔哩哔哩_bilibili

状态:之前做过一次,有哈希表、快慢指针两种方法

快慢指针:

判断链表是否有环

快指针每次走两个节点,慢指针每次走一个节点,相当于快指针每次以移动一个节点的速度靠近慢指针;快慢指针会相遇则说明存在环。

如果有环,如何找到这个环的入口

假设两指针在图中紫色圆点处相遇,那么快指针走过的路程为a+n(b+c)+b,n为绕环走的圈数,n >= 1因为肯定是快指针至少转完一圈后,在后面追慢指针,而后二者相遇;慢指针走过的路程为a+b;任意时刻,快指针走过的距离都为慢指针的 2 倍,则有等式a+n(b+c)+b = 2(a+b),

要找环的入口,需要求a,根据等式 a = n(b+c) - b ;整理有 a  = (n-1)(b+c) + c,当n=1时,有a=c,说明一个指针在头节点,一个指针在相遇点,两个指针同时移动,会在环的入口处相遇。当n>1时,就是从相遇点出发的指针多在环中走了几圈,但总会在入环处相遇。

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
        fast, slow = head,head
        while fast and fast.next: # 因为fast一次移动两步,所以需要判断fast.next
            fast = fast.next.next
            slow = slow.next 
            if fast == slow: # 说明二者相遇,存在环
                index1 = slow
                index2 = head
                while index1 != index2:
                    index1 = index1.next
                    index2 = index2.next
                return index1 # 环的入口点
        return None

总结

虚拟头节点

        一般涉及到 增删改操作,用虚拟头节点都会方便很多;如果只能查的话,用不用虚拟头结点都差不多。

链表的基本操作

反转链表

删除倒数第N个节点

链表相交

环形链表

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。