代码随想录 day4:第二章 链表part02

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

用虚拟头结点,这样会方便很多。

题目:给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
在这里插入图片描述
思路:使用虚拟头结点会方便很多,要不然每次针对头结点(没有前一个指针指向头结点),还要单独处理。接下来就是交换相邻两个元素了,此时一定要画图,不画图,操作多个指针很容易乱,而且要操作的先后顺序需要搞清楚。
在这里插入图片描述

# 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: ListNode) -> ListNode:
        dummy_head = ListNode(next=head)
        current = dummy_head
        
        # 必须有cur的下一个和下下个才能交换,否则说明已经交换结束了
        while current.next and current.next.next:
            temp = current.next # 防止节点修改
            temp1 = current.next.next.next
            
            current.next = current.next.next
            current.next.next = temp
            temp.next = temp1
            current = current.next.next
        return dummy_head.next

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

双指针的操作,要注意,删除第N个节点,那么当前遍历的指针一定要指向第N个节点的前一个节点。

题目:给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
在这里插入图片描述
尝试使用一趟扫描实现
思路:双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。
算法:

  • 使用虚拟头结点,这样方便处理删除实际头结点的逻辑
  • 定义fast指针和slow指针,初始值为虚拟头结点
  • fast首先走n + 1步 ,为什么是n+1呢,因为只有这样同时移动的时候slow才能指向删除节点的上一个节点(方便做删除操作)
  • fast和slow同时移动,直到fast指向末尾
  • 删除slow指向的下一个节点
# 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)
        cur=cur1=dummy_head
        count=0
        #计算链表的长度
        while cur.next:
            count+=1
            cur=cur.next
        # print(count)
        #删除链表,找到要删除节点的前一个节点
        while count-n:
            cur1 = cur1.next
            count-=1
        # 删除节点
        cur1.next=cur1.next.next
        return dummy_head.next
        
        #  第二种方式
        # # 扫描一遍
        # # 创建一个虚拟节点,并将其下一个指针设置为链表的头部
        # dummy_head = ListNode(0, head)

        # # 创建两个指针,慢指针和快指针,并将它们初始化为虚拟节点
        # slow = fast =dummy_head

        # # 快指针比慢指针快 n+1 步
        # for i in range(n+1):
        #     fast = fast.next
        
        # # 移动两个指针,直到快速指针到达链表的末尾
        # while fast:
        #     slow=slow.next
        #     fast=fast.next
        # ##注意:当fast指向空指针时,slow应该在倒数n+1的地方
        
        # slow.next=slow.next.next

        # return dummy_head.next

面试题 02.07. 链表相交

数值相同,不代表指针相同

题目:给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。图示两个链表在节点 c1 开始相交:
在这里插入图片描述
思想:

  • 相交意味着两个链表从某个节点开始共享相同的节点引用,而不仅仅是节点的值相同。这意味着链表中的相交点是指向同一个内存地址的节点对象。
  • 简单来说,就是两个链表点的指针交点,不是数值相等,而是指针相等。看两个链表,目前curA 指向链表A的头点,curB指向链表B的头点,求出两个链表的长度,并求出两个链表长度的差值,然后让curA对齐,和curB空格的位置,如图
    在这里插入图片描述
    在这里插入图片描述
  • 此时我们可以比较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:
        # 解法1:求长度,同时出发
        lenA,lenB=0,0
        #求A的长度
        cur=headA
        while cur:
            lenA+=1
            cur=cur.next
        #求B的长度
        cur=headB
        while cur:
            lenB+=1
            cur=cur.next
        #下面是A更长时运行的代码,就算B更长,也让其变换一下,使得A最长
        curA,curB=headA,headB
        if lenB>lenA:
            curA, curB = curB, curA
            lenA, lenB = lenB, lenA 
            
        # 将A和B的末端对齐
        for i in range(lenA-lenB) :
            curA=curA.next
        # 进行判断   
        for i in range(lenB):
            if curA!=curB:
                curA=curA.next
                curB=curB.next
            else:
                return curA
        return None
        

        

142.环形链表II

比较有难度的题目,确定环和找环入口。

题目:给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。不允许修改链表。
在这里插入图片描述
思路:

  • 判断链表是否有环

    • 小学经典问题,可以使用快慢指针,如果有环,快指针一定可以追赶上慢指针。
    • 具体来说,分别定义快和慢指针,从头结点出发,快指针每次移动两个节点,慢指针每次移动一个节点,如果快和慢指针在途中相遇,说明这个链表有环。
  • 如果有环,如何找到这个环的入口

    • 假设从头结点到环形入口节点的节点数为x。 环形入口节点到快指针与慢指针相遇节点数为y。 从相遇节点再到环形入口节点数为z。 以下是所示:
      在这里插入图片描述
    • 相遇时:slow指针走过的节点数为: x + y, fast指针走过的节点数:x + y + n (y + z),n为fast指针在环内走了n圈才遇到slow指针, (y+z)为一圈内节点的个数A。
    • 因为快指针是一步走两个节点,慢指针一步走一个节点,所以快指针走过的节点数 = 慢指针走过的节点数 * 2:
    • (x + y) * 2 = x + y + n (y + z),故x + y = n (y + z)
    • 因为要找环形的入口,那么要求的是x,因为x表示头结点到环形入口节点的距离。所以要求x ,将x单独放在左面:x = n (y + z) - y
    • 整理公式之后为如下公式:x = (n - 1) (y + z) + z注意这里n一定大于等于1的,因为快指针至少要多走一圈才能碰到慢指针。
      在这里插入图片描述

    当n=1时,x = z,慢指针和快指针同时从头节点出发,慢指针步长为1,快指针步长为2,两者在B处相遇,想要到达A处,还需要z步,刚好x=z,让慢指针回到头节点,同时快指针的步长设为1,两者同时出发,同时走z步之后在A处相遇。

    n>1时,快指针在环形转n圈之后才遇到慢指针。其实周期和n为1的时候效果都是相同的,一样可以通过这个方法找到循环的入口节点,只不过,index1指向环里多转了(n-1)圈,通常会遇到index2,相遇点依然是循环的入口节点。

# 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]:
        slow=fast=head 
        # 1.判断是否有环
        while fast and fast.next:
            slow=slow.next
            fast=fast.next.next

            if slow==fast:#2.相遇之后,判断入口
                slow=head
                while slow!=fast:#一直判断,直到相遇
                    slow=slow.next
                    fast=fast.next
                return slow
        return None 

总结

什么时候使用虚拟头结点,什么时候不用虚拟头结点。一般涉及到增删改操作,用虚拟头结点都会方便很多, 如果只能查的话,用不用虚拟头结点都差不多。也可以方便记忆,统一都用虚拟头结点。

  • 链表的种类为:单链表,双链表,循环链表
  • 链表的存储方式:链表的节点在内存中是分散存储的,通过指针放在一起。
  • 链表流量进行增删改查。
  • 虚拟头节点:链表的一大问题就是操作当前节点必须要找前一个节点才能操作。这个操作捕获了头结点的尴尬,因为头结点没有前一个节点。每次分组情况都要单独处理,所以使用虚拟分组的技巧,就可以统一这种技巧。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值