【剑指offer】链表相关题解析+Python代码

本文深入解析链表数据结构的多种经典算法,包括寻找倒数第k个节点、链表反转、环检测与入口节点定位、重复节点删除、两链表合并及首个公共节点查找。通过示例代码详解算法原理与实现技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

链表中倒数第k个节点

输入一个链表,输出该链表中倒数第k个结点。

借助列表存储法

    class Solution:
        def FindKthToTail(self, head, k):
            if not head:
                return None
            a=[]
            while head:
                a.append(head)
                head=head.next
            if k>len(a) or k<1:
                return None
            return a[-k]
  • 快慢指针法
    单链表没有办法从后往前遍历节点,当我们从头开始遍历的时候,倒数第k个节点即正数第n-k+1个节点,但这样需要知道链表的长度,所以总共需要两次遍历。
    快慢指针的方法即用两个指针同时指向头结点,快指针先走k-1步,然后快慢指针同时开始走,一直相隔k-1个指针,当快指针走到结尾的时候,慢指针刚好为倒数第k个节点。

这里有一个需要注意的点即k值不要超过链表长度。

  • 算法注意点:
    算法我们将快指针往前走了k步,这样可以判断k是否大于链表的长度,即遍历过程中看p1是否存在,不存在即大于返回空。
    在慢指针走的过程中,由于我们的while 循环是以p1为判断条件,所以当p1.next不存在了即p1走到了最后一个节点,还是会再循环一次,这个和上边的p1先走k-1步就对应上了。即我们的算法p1多走了1步,但是下边的循环也使得p2多走了一步,所以是对应的。
    class Solution:
        def FindKthToTail(self, head, k):
            p1=p2=head
            for i in range(k):
                if not p1:
                    return None
                p1=p1.next
            while p1:
                p1=p1.next
                p2=p2.next
            return p2

反转链表

输入一个链表,反转链表后,输出新链表的表头。

  • 解法思路
    相当于加了一个指针,last=None,我们一直让last做phead的下一个节点,即phead.next=last,然后再把phead变为last,phead=phead.next这样循环
    class Solution:
        # 返回ListNode
        def ReverseList(self, phead):
            if not phead or not phead.next:
                return phead
            last=None
            while phead:
                temp=phead.next
                phead.next=last
                last=phead
                phead=temp
            return last

链表中环的入口节点

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

  • 解题思路:
    第一是确定链表是否包含环。运用快慢指针,快指针走两步,慢指针走一步,如果两个的值会相等,即说明链表包含环。
    第二步如何找到环的入口。如果链表的环有n个节点,快指针先走n步,然后快慢指针同时走,直到相遇的那个节点就是环的入口节点。

第三步如何得到环中节点的数目。在判断环的基础上,在遇到相等的节点后,快慢指针继续走,直到再回到最初相等的节点,便可得到环中节点数了。

  • 算法思路:
    //先说个定理:两个指针一个fast、一个slow同时从一个链表的头部出发 //fast一次走2步,slow一次走一步,如果该链表有环,两个指针必然在环内相遇 //此时只需要把其中的一个指针重新指向链表头部,另一个不变(还在环内), //这次两个指针一次走一步,相遇的地方就是入口节点。
    class Solution:
        def EntryNodeOfLoop(self, pHead):
            if not pHead or not pHead.next:
                return None
            p1=p2=pHead
            p2=pHead.next
            p1=pHead.next.next
            while p1.val!=p2.val:
                p1=p1.next.next
                p2=p2.next
            p2=pHead
            while p1.val!=p2.val:
                p1=p1.next
                p2=p2.next
            return p2

删除链表中重复的节点

    class Solution:
        def deleteDuplication(self, pHead):
            if not pHead or not pHead.next:
                return pHead
            new_head=ListNode(-1)
            new_head.next=pHead
            
            pre=new_head
            p = pHead  # cur node
            next = None
            while p and p.next:
                next = p.next  # later node
                if p.val == next.val:
                    while next and next.val == p.val:
                        next = next.next
                    p = next
                    pre.next = p
                else:
                    pre = p
                    p = next
            return new_head.next

合并两个排序的链表

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

  • 递归解法: 主要思想就是每次的下一个节点都是比较后值小的那个节点,所以每次建立一个mergeHead=None,然后赋给它小的值。如果两个中有一个不存在了,那么就把剩下的合并。
    class Solution:
        #返回合并后列表
        def Merge(self, pHead1, pHead2):
            # write code here
            if pHead1 == None:
                return pHead2
            elif pHead2 == None:
                return pHead1
    
            mergepHead = None
            if pHead1.val <= pHead2.val:
                mergepHead = pHead1
                mergepHead.next = self.Merge(pHead1.next, pHead2)
            elif pHead1.val > pHead2.val:
                mergepHead = pHead2
                mergepHead.next = self.Merge(pHead1, pHead2.next)
    
            return mergepHead

  • 非递归解法: 通过类建立一个新的头结点,每当这时候就要建立两个实例,一个用于循环,一个用于输出。
      class Solution:
            #返回合并后列表
            def Merge(self, pHead1, pHead2):
                # write code here
                dummy = ListNode(0)
                pHead = dummy
        
                while pHead1 and pHead2:
                    if pHead1.val >= pHead2.val:
                        dummy.next = pHead2
                        pHead2 = pHead2.next
                    else:
                        dummy.next = pHead1
                        pHead1 = pHead1.next
        
                    dummy = dummy.next
                if pHead1:
                    dummy.next = pHead1
                elif pHead2:
                        dummy.next = pHead2
                return pHead.next

两个链表的第一个公共结点

输入两个链表,找出它们的第一个公共结点。

  • 解题思路:
    两个单向链表在从第一个公共节点之后所有的节点都是重合的,不可能再出现分叉。拓扑形状看起来像一个Y。

在这里插入图片描述

所以说公共节点出现在链表的尾部,那么我们从后往前比较,遇到的第一个相同的节点就是要找的节点。符合“后进先出”原则,所以我们可以借助栈做辅助空间来比较,不过这样需要空间复杂度O(m+n)

之所以需要用到栈,是因为我们相同时遍历到链表的尾节点,当两个链表的长度不相同时,如果我们从头开始遍历,到达尾节点的时间就不一样。解决办法即:首先遍历两个链表得到他们的长度,在第二次遍历的时候,在较长的链表上先走若干步,接着同时在两个链表上遍历,找到的第一个相同的节点就是他们的第一个公共节点。

  • 算法注意点
    求长度时因为要移动节点的指针,所以直接求的话最终的P1、p2是为空的,所以把他放在一个函数里。
class Solution:
    def FindFirstCommonNode(self, p1, p2):
        if not p1 or not p2:
            return None
        len1=self.length(p1)
        len2=self.length(p2)
        sub=abs(len1-len2)
        if len1>len2:
            plong=p1
            pshort=p2
        else:
            plong=p2
            pshort=p1
        for i in range(sub):
            plong=plong.next
        while plong!=None and pshort!=None and plong.val!=pshort.val:
            plong=plong.next
            pshort=pshort.next
        return plong
    def length(self,p):
        length=0
        while p:
            p=p.next
            length+=1
        return length
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值