剑指offer(Python版本)-- 链表

链表

在这里插入图片描述
单链表:在这里插入图片描述
链表每一个结点的结构如下:

class ListNode:
     def __init__(self, x):
         self.val = x  # 链表的值
         self.next = None # 链表指向的下一个结点

如何实现一个链表? 如下所示:

class ListNode:
     def __init__(self, x):
         self.val = x  # 链表的值
         self.next = None # 链表指向的下一个结点
def printChain(node):
    while node:
        print(node.val)
        node=node.next
    
if __name__ == '__main__':
    # 1 -> 2 -> 3 -> None
    l1=ListNode(1) # 定义一个链表结点
    l2=ListNode(2)
    l3=ListNode(3)
    l1.next=l2
    l2.next=l3
    l3.next=None
    printChain(l1)

在这里插入图片描述

3. 从尾到头打印链表

题目描述:
输入一个链表,按链表值从尾到头的顺序返回一个ArrayList。
实现代码如下:

# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    # 返回从尾部到头部的列表值序列,例如[1,2,3]
    def printListFromTailToHead(self, listNode):
        # write code here
        # 遍历每一个listNode,
        # 把每一个node值循环放到(插入)列表的最前面,以此来实现倒叙
        res=[]
        pTmp = listNode # 一般用p代表指针,Tmp代表临时数据
        while pTmp:
            res.insert(0,pTmp.val) # 每一次循环都往头部添加(插入),放在列表的最前面
            pTmp = pTmp.next
        return res

题外话:python的list其实就是用链表(多个数据用一个指针)来实现动态增加长度

14. 链表中倒数第k个结点

题目描述:
输入一个链表,输出该链表中倒数第k个结点。
实现代码如下:

# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def FindKthToTail(self, head, k):
        # write code here
        #定义两个指针
        firstNode=head 
        secondNode=head
        for i in range(k):
            if firstNode==None: # 判断是否为空链表
                return None
            firstNode=firstNode.next # 第一个指针先走k步
        while firstNode:
            firstNode=firstNode.next
            secondNode=secondNode.next # 两个指针同时走,直到第一个指针走完
        return secondNode # 此时第二个指针所在位置就是倒数第k个结点

15. 反转链表(★★)

题目描述:
输入一个链表,反转链表后,输出新链表的表头。
实现代码如下:

# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    # 返回ListNode
    def ReverseList(self, pHead):
        # write code here
        # 将现有的头换成尾,尾部的next为空
        # 将从第二个node开始,循环将next指向前一个
        # 需要一直有一个指针指向还没有翻转的链表的头部
        
        # 需要考虑链表的长度
        if pHead == None:
            return None
        if pHead.next == None:
            return pHead
        
        leftpointer = pHead
        midpointer = pHead.next
        rightpointer = midpointer.next
        
        leftpointer.next = None
        while rightpointer:
            midpointer.next = leftpointer
            leftpointer = midpointer
            midpointer = rightpointer
            rightpointer = rightpointer.next
        midpointer.next = leftpointer
        return midpointer

16. 合并两个排序的链表(★★)

题目描述:
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
解题思路:
1)递归

  • 终止条件:当两个链表都为空时,表示我们对链表已合并完成。
  • 如何递归:我们判断 pHead1 和 pHead2 头结点哪个更小,然后较小结点的 next 指针指向其余结点的合并结果。(调用递归)

图解如下:
在这里插入图片描述
2)一般解法
利用几个指针,对两个链表的值进行比较,用当前指针指向比较后的较小值,不断循环,直至其中一个指针指向null。
实现代码如下:

# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    # 返回合并后列表
    def Merge(self, pHead1, pHead2):
        # write code here
        '''
        # 递归解法
        if not pHead1:return pHead2 # 终止条件,知道两个链表都为空
        if not pHead2:return pHead1
        if pHead1.val<=pHead2.val: # 比较的是链表中的值
            pHead1.next = self.Merge(pHead1.next,pHead2) # 在类中,调用函数自身也需要用self
            return pHead1
        else:
            pHead2.next = self.Merge(pHead1,pHead2.next)
            return pHead2
        '''
        # 普通解法
        # 利用了四个指针(pHead,prepointer,pTmp1,pTmp2)解题
        if pHead1==None:
            return pHead2
        if pHead2==None:
            return pHead1
        pHead = pHead1 if pHead1.val<pHead2.val else pHead2
        prepointer = pHead
        pTmp1=pHead1
        pTmp2=pHead2
        if pHead == pTmp1:
            pTmp1 = pTmp1.next
        else:
            pTmp2 = pTmp2.next
        while pTmp1 and pTmp2:
            if pTmp1.val<=pTmp2.val:
                prepointer.next = pTmp1
                prepointer = pTmp1
                pTmp1 = pTmp1.next
            else:
                prepointer.next = pTmp2
                prepointer = pTmp2
                pTmp2 = pTmp2.next
        if pTmp1==None:
            prepointer.next = pTmp2
        else:
            prepointer.next = pTmp1
        return pHead

25. 复杂链表的复制(★)

题目描述:
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
解题思路:
第一步:在链表的每一个结点(node)后面复制一个一样的node
第二步:实现新node的random指向
第三步:断开原来的node和新的node之间的链接

注意: 边界条件(特殊情况)一定要考虑进去!!!
在这里插入图片描述
实现代码如下:

# class RandomListNode:
#     def __init__(self, x):
#         self.label = x
#         self.next = None
#         self.random = None
class Solution:
    # 返回 RandomListNode
    def Clone(self, pHead):
        # write code here
        # 在链表的每一个结点(node)后面复制一个一样的node
        pTmp = pHead
        # 这里需要判断pTmp是否为空,这是边界条件(特殊情况),一定要记得!!!
        if pTmp==None:
            return None
        while pTmp:
            # 首先建立一个node(根据上面的链表结构建立)
            node = RandomListNode(pTmp.label)
            node.next = pTmp.next
            pTmp.next = node
            pTmp = node.next
            
        # 实现新node的random指向
        pTmp = pHead
        while pTmp:
            # 这里需要判断pTmp.random是否为空,为空的话则.next不存在,一定要记得!!!
            if pTmp.random:
                pTmp.next.random = pTmp.random.next
            pTmp = pTmp.next.next
            
        # 断开原来的node和新的node之间的链接
        pTmp = pHead
        pnewTmp = pHead.next
        res = pHead.next
        while pTmp:
            pTmp.next = pTmp.next.next
            # 这里需要判断边界条件。当pnewTmp.next为空时,pnewTmp.next.next不存在,一定要记得!!!
            if pnewTmp.next:
                pnewTmp.next = pnewTmp.next.next
            pTmp = pTmp.next
            pnewTmp = pnewTmp.next
        return res

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

题目描述:
输入两个链表,找出它们的第一个公共结点。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)
实现代码如下:

# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    def FindFirstCommonNode(self, pHead1, pHead2):
        # write code here
        pTmp1 = pHead1
        pTmp2 = pHead2
        while pTmp1 and pTmp2:
            # 这里需要考虑两个链表长度一样的情况!!
            if pTmp1 == pTmp2:
                return pTmp1
            # 当链表长度不一样时的情况,进行下一步操作
            pTmp1 = pTmp1.next
            pTmp2 = pTmp2.next
            
        # 封装
        # 第一个参数是比较短的链表指针
        # 第二个参数是比较长的链表指针
        # 第三个参数是比较短的链表表头
        # 第四个参数是比较长的链表表头
        def findnode(shortpointer,longpointer,shortHead,longHead):
            k = 0
            # 寻找两链表长度之间的差值k
            while longpointer:
                longpointer=longpointer.next
                k += 1
            # 先让长的链表走k步
            shortpointer = shortHead
            longpointer = longHead
            for i in range(k):
                longpointer = longpointer.next
            while shortpointer != longpointer:
                shortpointer = shortpointer.next
                longpointer = longpointer.next
            return shortpointer
        
        if pTmp1:
            return findnode(pTmp2,pTmp1,pHead2,pHead1)
            '''
            k = 0
            while pTmp1:
                pTmp1=pTmp1.next
                k += 1
            pTmp1 = pHead1
            pTmp2 = pHead2
            for i in range(k):
                pTmp1 = pTmp1.next
            while pTmp1 != pTmp2:
                pTmp1 = pTmp1.next
                pTmp2 = pTmp2.next
            return pTmp1
                '''
        
        if pTmp2:
            return findnode(pTmp1,pTmp2,pHead1,pHead2)
            '''
            k = 0
            while pTmp2:
                pTmp2=pTmp2.next
                k += 1
            pTmp1 = pHead1
            pTmp2 = pHead2
            for i in range(k):
                pTmp2 = pTmp2.next
            while pTmp1 != pTmp2:
                pTmp1 = pTmp1.next
                pTmp2 = pTmp2.next
            return pTmp1
                '''

46. 孩子们的游戏(圆圈中最后剩下的数)

题目描述:
每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)
如果没有小朋友,请返回-1
解题思路:
这其实就是一个约瑟夫环的简化版本,
约瑟夫环的公式推导可参见链接:约瑟夫环——公式法(递推公式)
实现代码如下:

class Solution:
    def LastRemaining_Solution(self, n, m):
        # write code here
        # 通过推导公式可得:f(n)=(f(n-1)+m)%n
        if n<1 and m<1:
            return -1
        if n==1:
            return 0
        value=0
        for index in range(2,n+1):
            currentValue = (value + m)%index
            value = currentValue
        return value

55. 链表中环的入口结点

题目描述:
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
解题思路:

  1. 设置快慢指针fast和low,fast每次走两步,low每次走一步。假如有环,两者一定会相遇(因为low一旦进环,可看作fast在后面追赶low的过程,每次两者都接近一步,最后一定能追上)。
  2. 相遇时,设
    链表头到环入口长度为–s
    环入口到相遇点长度为–d
    相遇点到环入口长度为–m
    可得出式子:s=(n-1)(m+d)+d ,意思是: 链表头到环入口的距离=相遇点到环入口的距离+(n-1)圈环长度。其中n>=1,所以n-1>=0圈。
  3. 所以两个指针分别从链表头和相遇点继续出发,每次走一步,最后一定相遇于环入口。
    在这里插入图片描述

实现代码如下:

# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    def EntryNodeOfLoop(self, pHead):
        # write code here
        # 需要定义两个指针,其中一个跳两步(fast),一个跳一步(slow)
        # 循环跳
        # 要么是快的指针为空(没有环),要么是快慢指针相遇(有环)
        fast = pHead
        slow = pHead
        if pHead == None:
            return None
        while fast and fast.next:
            fast = fast.next.next
            slow = slow.next
            if fast == slow:
                break
        if fast == None or fast.next == None:
            return None
        # 如果slow走了L长度,那么fast就走了2L长度
        # 假设从开始到入口点的长度是s,slow在环里里面走的长度是d
        # 那么L=s+d
        # 假设环内slow没走的长度是m,fast走的长度就是2L=s+n(m+d)+d
        # 带入s+n(m+d)+d=2*(s+d),得出s=m+(n-1)(m+d)
        # 从以上公式可以得出,两个指针分别从链表头和相遇点出发,最后一定相遇于环入口。 
        fast = pHead
        while fast != slow:
            fast = fast.next
            slow = slow.next
        return fast
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值