代码随想录 Day 4 | 24.两两交换链表中的节点、19.删除链表的倒数第N个节点、面试题 02.07. 链表相交、142.环形链表II 、【链表】总结

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

题目链接/文章讲解/视频讲解: 代码随想录

1. 看到这道题的第一想法

        定义一个虚拟头节点dummy_head,以及一个临时节点current用于遍历链表,以及临时节点pre表示current.next;然后进入循环(循环条件是pre.next不为空),定义临时指针temp用于存放下一组交换节点的第一个节点;接下来开始交换节点,也就是pre指向current,而current.next指向temp。最后返回虚拟头节点next,就是链表新的头节点。

        这样做最后系统显示超时。

2. 看完代码随想录的想法

(1)定义dummy_head:但是要思考清楚dummy_head的作用是为了统一操作规则,防止直接操作头节点,改变头节点的值,最后无法返回交换后的头节点;

        定义current指针:用于遍历链表节点,由于要交换两个节点,所以需要找到交换节点的上一个节点才可以进行交换操作,所以此处需要初始化current指针指向虚拟头节点dummy_head。

(2)进入遍历交换操作:

        首先思考清楚边界条件作为循环的条件入口:当节点数为偶数时,循环的终止条件是current.next指向NULL,当节点数为奇数时,循环的终止条件是current.next.next指向NULL;因此只有当current.next不指向NULL并且current.next.next不指向NULL,才进行交换节点的操作。

        注意:进入循环的两个条件的顺序不可以交换,否则在判定过程中将产生空指针异常。也就是说两个判断条件不可交换。

 while current.next and current.next.next:

        接下来开始交换节点:首先为了防止交换节点后操作不了下一个节点,先将需要交换的第一个节点使用临时指针temp进行保存;然后将current.next指向current.next.next,然后新的current.next的next应该指向的是temp,即current.next.next=temp,而temp的next应该指向未改变节点前的current.next.next.next,而进行操作后current.next.next.next已经发生了变化,因此current.next.next.next也应该在交换节点前用临时指针temp1进行保存,那么此时temp.next = temp1,完成了循环一次的交换操作。接下来,应该移动current指针让它进入下一次循环,也就是current=current.next.next,直接让它向后移动两个节点即可,不需要管他后面两个节点是什么。

(3)最后返回dummy_head.next即为交换完成后的新链表头节点。

3. 实现过程中遇到的困难及解决

(1)如果不思考清楚边界条件,容易陷入空指针异常或无限死循环

空指针异常:如果这个指针为空,而对它进行操作,就会出现空指针异常,所以在进行操作时,必须思考清楚是否会有对空指针进行操作,从而提前对它进行限制。

(2)要在进行操作前来缓存交换操作会影响的节点,否则在交换后将无法找到需要进行操作的节点,会改变节点,造成逻辑错误。

(3)要考虑清楚current指针初始化到底应该指向dummy_head还是dummy_head.next,这取决于循环遍历规则的一致性,在本题中由于需要交换两个节点,那么必须找到该节点的上一个节点,所以此处current初始化指向为dummy_head。

(4)在进入下一次循环前,需要移动current指针,让它直接向后移动两个节点即可

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

题目链接/文章讲解/视频讲解: 代码随想录

1. 看到这道题的第一想法(以下是AC的代码)

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

class Solution:
    def get_length(self,head: ListNode) -> int:
        length = 0
        current = head
        while current:
            length += 1
            current = current.next
        return length

    def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
        dummy_head = ListNode(next=head)
        if n < 1 or n > self.get_length(head):
            return -1
        else:
            current = dummy_head
            for i in range(self.get_length(head) - n ):
                current=current.next
            current.next = current.next.next
        return dummy_head.next

(1)定义一个虚拟头节点dummy_head;然后定义一个current由于循环遍历链表,初始化指向dummy_head。

(2)根据归纳法发现,要删除倒数第n个节点,则要删除的节点下标为链表长度-n,因此为了删除倒数第n个节点,需要将current指向倒数第n个节点的前一个节点就停止遍历,即当current指向下标为size-n-1的节点,就跳出循环。

(3)接下来就进行删除节点的操作,也就是current.next = current.next.next。

(4)最后返回dummy_head.next即可。

(5)这种方法可以AC,但需要注意链表不可以直接self.size来获取长度,而应该定义一个函数来计算链表长度。

def get_length(self, head:ListNode):
    current = head
    length = 0
    while current:
        length+=1
        current=current.next
    return length

2. 看完代码随想录的想法

        巧妙的快慢指针思路,值得学习:

(1)先定义虚拟头节点,因为如果要删除的是头节点head,那么需要单独讨论,所以为了避免这种情况,采用虚拟头节点的方式统一处理。

(2)定义快慢指针,初始化都指向虚拟头节点。

(3)快指针先移动n步,然后快慢指针再一起移动,直到快指针指向NULL就停止移动,此时慢指针刚好指向的是需要删除的倒数第N个节点的前一个节点,然后退出循环遍历。

(4)慢指针的next直接指向next的next,就完成了删除倒数第N个节点的操作。

(5)最后返回虚拟头节点的next。

三、面试题 02.07. 链表相交

题目链接/文章讲解: 代码随想录

1. 看到这道题的第一想法

(1)暴力解法:先遍历一遍A链表,然后嵌套遍历B链表,找到链表节点完全相同的一段子链表,最后返回即可。

(2)反向从后往前遍历链表,但不知如何实现。

(3)看了提示后成功AC:

        A.首先,为了获取A和B链表的长度,所以定义取链表长度的函数 ;

        B.然后,在主函数中取得A、B链表的长度,并计算AB长度的差值diff;接下来分别定义currentA和currentB来对A、B链表进行遍历循环;

        C.分类讨论A和B链表长度是否相同、大于、小于:

        当diff=0时,表明A和B链表长度相同,那么以currentA和currentB不为null作为进入循环的条件,首先判断currentA和currentB两个指针是否指向的是同一节点,如果是的话返回任一节点即可,如果不是则两个指针一起向下循环。

        当diff>0的时候,表明A比B长,那么采用for循环,让currentA先走diff步,然后再和diff=0时的做法即可(同上);

        当diff<0的时候,表明B比A长,那么采用for循环,让currentB先走diff步,然后再和diff=0时的做法即可(同上);(特别注意:此时diff<0且diff是int类型,所以不可以写成:for i in range(diff),会报错,而应该在diff外面嵌套abs函数,将负数取成整数!!)

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

class Solution:
    def get_length(self,head:ListNode):
        length = 0
        current = head 
        while current:
            length += 1
            current=current.next
        return length

    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        currentA = headA
        currentB = headB
        diff = self.get_length(headA) - self.get_length(headB)
        if diff == 0:
            while currentA and currentB:
                if currentA == currentB:
                    return currentA
                else:
                    currentA=currentA.next
                    currentB=currentB.next
            
        elif diff > 0:
            for i in range(diff):
                currentA=currentA.next
            while currentA and currentB:
                if currentA == currentB:
                    return currentA
                else:
                    currentA=currentA.next
                    currentB=currentB.next
        else:
            for i in range(-diff):
                currentB=currentB.next
            while currentA and currentB:
                if currentA == currentB:
                    return currentB
                else:
                    currentA=currentA.next
                    currentB=currentB.next

2. 看完代码随想录的想法

同上

3. 实现过程中遇到的困难及解决

(1)当diff<0时,diff是int类型,所以不可以写成:for i in range(diff),会报错,而应该在diff外面嵌套abs函数,将负数取成整数!!

(2)解决这道题目如果没有思路的话,可以先考虑最简单的情况,也就是A、B链表长度一样的时候如何写,然后再去考虑长度不一样应该如何处理,由特殊情况推广至一般情况。

四、142.环形链表II

题目链接/文章讲解/视频讲解: 代码随想录

1. 看到这道题的第一想法

        首先定义一个虚拟头节点,然后定义快慢指针指向虚拟头节点,然后快指针先移动几个节点,慢指针再开始移动,直到两个指针相遇就是开始进入循环的第一个节点。

        问题在于不知道快指针到底先移动多少步。

2. 看完代码随想录的想法

(1)要先思考清楚如何能够获取到进入环的第一个节点:

        首先,定义快慢指针,快指针每次移动2步,而慢指针每次移动一步(因为二者的相对速度为1,所以在进入环内进行循环时,快指针追上慢指针不会跳过慢指针,并且快指针一定是在慢指针进入环内第一圈就可以追上);

        然后,明确这道题目的关键在于获取慢指针离进入环的第一个节点的距离是多少,可以设参数解方程得到,具体推导如下:

需要注意的是快指针和慢指针相遇不一定是在进入环的第一个节点!

        由上图可以推导出,在快慢指针相遇后,一个指针从相遇点出发,而另一个指针从头节点出发,以相同的速度移动,最终相遇的第一个节点就是进入环的第一个节点

(2)开始写代码:

        首先定义快慢指针均从头节点出发,然后因为快指针速度为2,比慢指针先将链表遍历,所以快指针的next以及next.next不为空时,进入循环

        然后由于快慢指针从同一起点出发,所以需要先让快慢指针以各自的速度移动后,再进行判别,判别快慢指针是否相遇;

        如果快慢指针相遇,那么得到相遇点,此时我们将其中一个指针移动回头节点,另一个指针从相遇点出发,以相同的速度移动,直到相遇就是我们需要求得的环的第一个节点;

        如果没有环,则输出None。

五、【链表】总结

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

当然也可以为了方便记忆,统一都用虚拟头结点。

代码随想录

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值