leetcode刷题 链表相关 python

本文详细解析了LeetCode中涉及链表的多个题目,包括从尾到头打印链表、反转链表、合并两个排序链表、找到两个链表的第一个公共结点等。介绍了多种解题方法,如栈、递归、双指针等,并针对每道题目给出了具体的Python代码实现,强调了空间和时间复杂度的优化。

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

—————剑指offer—————

easy

1)JZ6 从尾到头打印链表

题目描述
在这里插入图片描述
*注意输入的是结点,输出的是数组

①方法一:打入栈,逆序输出

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

python3

class Solution:
    def printListFromTailToHead(self , listNode: ListNode) -> List[int]:
        # write code here 牛客里默认py3的格式
        stack = []
        while listNode:
            stack.append(listNode.val)
            listNode = listNode.next
        return stack[::-1]

listnode是头节点,List[list]返回一个列表

python2 的默认自定义函数的格式是

class Solution:
    def blaba(self, listnode):

liatnode头结点名

②方法二:递归 (但很有可能超过最大递归层数而报错)

class Solution:
    def printListFromTailToHead(self , listNode: ListNode) -> List[int]:
        # write code here
        return self.printListFromTailToHead(listNode.next) + [listNode.val] if listNode else []

注意
Ⅰ 方法自身间的回溯需要 + self

Ⅱ 同一个类,不同对象间的互相调用回溯需要 + self

Ⅲ 方法内的函数递归自身不需要self(如print方法里我再自定义一个函数进行递归便不需要self)

这里的第一参数self表示创建的是类实例

Ⅳ 注意这个if else的使用, blablabla = a 的执行是在if的条件下,否则就 = else里的那个东西

Ⅴ 注意[]与()的使用,第一遍自己打的时候,listnode.val外面写成了(),这样是错的

Ⅵ 注意 + 是拼接的意思

Ⅶ 注意使用递归可能会因为超过最大递归层数而报错,谨慎使用(此题在牛客使用递归没有通过)
递归思路

2)JZ24 反转链表

题目描述

给定单链表的头节点 head ,请反转链表,并返回反转后的链表的头节点
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

其实输入都是一样的,输入一个链表的头节点

①方法一:最简单的思路,取出值,再装入新的链表里

数组切片,但是需要额外的空间存储链表
时O(n) 空O(n)

class Solution:
    def ReverseList(self , head: ListNode) -> ListNode:
        # write code here
        stack = [] #把链表逆序存入一个数组里(从题 6学来)
        while head:
            stack.append(head.val)
            head = head.next
        stack = stack[::-1] #逆序数组
     #学会怎么去新建一个结点
        node = ListNode(0) #定义一个结点,给它填入值0,这个结点当作伪头结点
        p = node # p是指向这个结点的指针
        for num in stack: #意思是num从前往后取一遍stack里的值
            p.next = ListNode(num) #往后链接新的结点,填入stack的值
            p = p.next
        return node.next #输出真正开始表达含义的头结点,node.next才能取出真正的值

②方法二:在1的基础上简化一下,把列表想象成一个栈,把切片操作改成列表的pop操作

class Solution:
    def ReverseList(self , head: ListNode) -> ListNode:
        # write code here
        res = []
        while head:
            res.append(head.val)
            head = head.next
        #注意,这里就不需要逆序操作了,等着下面的pop即可
        node = ListNode(0)
        p = node
        while res:#当res列表不为空的时候
            p.next = ListNode(res.pop()) #注意这里pop函数要加()的
            p = p.next
        return node.next

注意:

Ⅰ 把列表名设置成stack老报错,改成别的就正常了,嗯,注意不要设置成特殊指代单词
Ⅱ res.pop() 默认pop尾部的那个

③方法三:双指针

时O(n) 空O(1)

class Solution:
    def ReverseList(self , head: ListNode) -> ListNode:
        # write code here
        cur = head #定义一个指针,和head所指向的位置一样,即头结点
        pre = None #定义一个指针,指向空,即None,理解为Null
        while cur: #直到cur==None就退出
        # 从左往后看去设计,不是从右往左看,要不然全乱套了
            tmp = cur.next #设置一个指针,记录cur的后继节点,要不然会丢失
            cur.next = pre # cur所在结点的箭头指向pre
       #注意第一次写成了 cur = pre,错误,这样是循环不起来的,cur只能代表指针,不代表结点情况
       #注意指针指向结点然后进行操作是为了改变结点之间的联系,而不是指针单方面更改位置
            pre = cur #pre指针更改位置,指向cur所指的结点
            cur = tmp #cur指针更改位置,指向tmp所指的结点,往后移
        return pre #输出新的头结点

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3)JZ25 合并两个排序的链表

题目描述
在这里插入图片描述
在这里插入图片描述
要求了空间复杂度o(1),考虑指针

①方法一:

时间O(M+N)
空间O(1) 节点引用 dum , cur 使用常数大小的额外空间,

思路:
在这里插入图片描述

class Solution:
    def Merge(self , pHead1: ListNode, pHead2: ListNode) -> ListNode:
        # write code here
        p = node = ListNode(0) #定义两个指针,p是之后要动的,node原地不动
        while pHead1 and pHead2: #注意逻辑用语是 and,如果这俩至少有一个为空
            if pHead1.val < pHead2.val:
                p.next = pHead1
                pHead1 = pHead1.next
            else:
                p.next = pHead2
                pHead2 = pHead2.next
            p = p.next #也可以分别写进if else里,就是有点冗余
            
        #if pHead1:
        #    p.next = pHead1
        #else: p.next = pHead2
        p.next = pHead1 if pHead1 else pHead2 #简便写法,如果phead1存在,就接phead1,否则接phead2
        return node.next

4)JZ52 两个链表的第一个公共结点

题目解释

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

①方法一:栈实现

先把两个链表存储在2个栈里面,找到链表的第一个公共结点即为倒着向前数最后一个一样的结点

时间复杂度o(n)
空间复杂度o(n)

class Solution:
    def FindFirstCommonNode(self , pHead1 , pHead2 ):#phead1和2是两个链表的头指针
        # write code here
        a, b = [], []
        p, q = pHead1, pHead2 #重新定义两个指针,这样原来的链表还在
        while p:
            a.append(p)
            #a.append(p.val) 不用这个是因为存入的就是结点,而不是值(存入了2个对象)
            p = p.next
        while q:
            b.append(q)
            q = q.next
        ans = None #定义一个空指针
        i = len(a)-1  #别忘记-1了,开始是0
        j = len(b)-1
        while i>=0 and j>= 0 and a[i] == b[j]: 
        #因为两个链表后续部分是共用的,所以可以直接==比较判断
        #while和if连用了
            ans = a[i]
            i = i-1
            j = j-1
        return ans #返回对应结点的头指针

①方法二:指针实现

时间:o(a+b) 最差情况下(即 |a - b| = 1, c = 0),此时需遍历 a + b个节点。
空间:o(1) 节点指针 A , B 使用常数大小的额外空间

思路
在这里插入图片描述
在这里插入图片描述

def FindFirstCommonNode(self, pHead1, pHead2):
        # write code here
        a, b = pHead1, pHead2
        while a != b:
            a = a.next if a else pHead2 #注意这里是phead,不是b
            b = b.next if b else pHead1 #注意这里是phead,不是a
            #理解为 
            '''
            if a:
                a = a.next
            else: 
                a = pHead2
            if b:
                b = b.next
            else:
                b = pHead1
            '''
        return a

5)JZ23 链表中环的入口结点(mid)

题目解释

在这里插入图片描述
在这里插入图片描述
方法:双指针

在这里插入图片描述
在这里插入图片描述
时间:o(n)
空间:o(1)

class Solution:
    def detectCycle(self, head: ListNode) -> ListNode:
        fast, slow = head, head #设置快慢指针,fast一次走2步,slow一次走一步,如果有环一定会相遇
        while True: #常用循环入口
            if not(fast and fast.next): return None 
            #判断fast是否为空,防止链表是空
            #判断fast.next是否为空,防止无环,而且如果fast.next为空的话,无法取到.next.next值
            #这里不判断fast.next.next,是因为下一轮还会判断,不必冗余
            fast = fast.next.next #跳2步
            slow = slow.next #跳1步
            if (fast == slow): #找第一次相遇的时候,就跳出循环
                break
            #第一次相遇的时候,不一定是在环入口
            #第一次相遇的时候,f走了2s步,且f=s+nb(从s的角度得出,走了s+绕b了n圈赶上f)
            #得出s=nb,f=2nb,s想要回到环入口得再走a步
            #a怎么来?刚好头结点到环入口就是a步,所以设置一个指针指向head,s跟着head走
            #之后第一次重合时,就是走了a步
        fast = head #fast的重新利用,回到头指针,寻找和s的第二次相遇(fast改位置后的第一次相遇)
        while fast != slow:
            fast = fast.next
            slow = slow.next
        return fast

6)JZ22 链表中倒数最后k个结点

题目解释

在这里插入图片描述
方法:双指针

fast指针一直和slow保持k个距离,当fast为null时,slow也就是倒数第k个结点

时间o(n)
空间o(1)

class Solution:
    def getKthFromEnd(self, head: ListNode, k: int) -> ListNode:
        fast, slow = head, head
        while k>0:
            fast = fast.next
            k = k-1
        '''
        这段也可以用for循环实现
        for _ in range(k):  或者是for num in range(k),num从0取到k-1
            fast = fast.next
        '''
        while fast:
            fast = fast.next
            slow = slow.next
        return slow

注意牛客上的要求稍微不同,考虑到链表长度可能小于k的情况

在这里插入图片描述

class Solution:
    def FindKthToTail(self , pHead: ListNode, k: int) -> ListNode:
        # write code here
        fast, slow = pHead, pHead

        for _ in range(k):
            if fast: fast = fast.next
            else: return None

        while fast:
            fast = fast.next
            slow = slow.next
        return slow

7)JZ18 删除链表的结点

题目解释
在这里插入图片描述
方法:双指针

注意删除结点操作是什么

时间o(n)
空间o(1)

class Solution:
    def deleteNode(self , head: ListNode, val: int) -> ListNode:
        # write code here
        fast, slow = head, head
        if fast is None: return None #排除空链表
        else:
            fast = fast.next
        if (head.val == val): return head.next #提前处理万一删除的是头结点
        while fast:
            if fast.val == val:
                slow.next = fast.next
                return head
            else:
                fast = fast.next
                slow = slow.next

8)JZ83 删除排序链表中的重复元素

题目解释
在这里插入图片描述
方法①:双指针

思路:

cur当作主指针,遍历链表,fast当作对比指针,寻找是否有相同的结点,如果有就当场删除

class Solution:
    def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
        cur, fast = head, head
        if cur is None: return None
        fast = cur.next
        while cur and fast:
            if cur.val == fast.val:
                cur.next = fast.next
                fast = fast.next
            else:
                cur = fast
                fast = fast.next
        return head

方法②:单指针

思路:

因为fast始终仅仅只比cur前一位,所以fast可以用cur.next代替
注意,还要注释掉重复的语句注释掉关于fast的更改语句(因为这里没有fast的定义!其实这两个好像是一个意思。。。反正是一定要注释掉关于fast指针的更改语句)

class Solution:
    def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
        cur = head
        if cur is None: return None
        #fast = cur.next
        while cur and cur.next:
            if cur.val == cur.next.val:
                cur.next = cur.next.next
                #cur.next = cur.next.next  重复的语句
            else:
                cur = cur.next
                #cur.next = cur.next.next  原本是更改fast的指向,这里不存在fast,所以删掉
        return head

牛客上的例题更改了一下,重复的结点要全部删掉

在这里插入图片描述
方法①:双指针

思路:

因为要删掉重复的所有结点,所以要用一个指针pre来存住前面的结点防止链表丢失

class Solution:
    def deleteDuplication(self, pHead: ListNode) -> ListNode:
        # write code here
        if not (pHead): return None
        node = ListNode(-1)  # 伪头结点
        node.next = pHead
 
        pre = node
        cur = node.next
        while cur and cur.next:
            if cur.val == cur.next.val:
                k = cur.val
                while cur and cur.val == k:
                    pre.next = cur.next
                    cur = cur.next
            else:
                pre = cur
                cur = cur.next
        return node.next

方法②:单指针

思路:

发现方法一中pre和cur始终差一个位置,所以cur可以用pre.next代替,注意先删除cur的全部定义语句!,然后把所有cur相关的改成pre.next(为了好看和习惯,也可以最后通过全部例题后把pre改成cur参数)

时间o(n)
空间o(1)

class Solution:
    def deleteDuplication(self, pHead: ListNode) -> ListNode:
        # write code here
        if not (pHead): return None
        node = ListNode(-1)  # 伪头结点
        node.next = pHead
 
        pre = node
        #cur = node.next
        while pre.next and pre.next.next:
            if pre.next.val == pre.next.next.val:
                k = pre.next.val
                while pre.next and pre.next.val == k:
                    pre.next = pre.next.next
                    #cur = cur.next 先注释掉
            else:
                pre = pre.next
                #cur = cur.next  先注释掉
        return node.next

mid

9)JZ35 复杂链表的复制

题目描述
在这里插入图片描述
思路:

首先注意!不能直接 return head!!

深拷贝和浅拷贝的区别,这里题目会去比较链表结点的地址,如果相同就会不通过,也就是说必须自己创建一个新的一模一样的的链表才可以。

普通的链表结点定义如下:

# Definition for a Node.
class Node:
    def __init__(self, x: int, next: 'Node' = None):
        self.val = int(x)
        self.next = next

给定链表的头节点 head ,复制普通链表很简单,只需遍历链表,每轮建立新节点 + 构建前驱节点 pre 和当前节点 node 的引用指向即可。

但是本题的结点定义如下:

# Definition for a Node.
class Node:
    def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
        self.val = int(x)
        self.next = next
        self.random = random

本题链表的节点新增了 random 指针,指向链表中的 任意节点 或者 null 。这个 random 指针意味着在复制过程中,除了构建前驱节点和当前节点的引用指向 pre.next ,还要构建前驱节点和其随机节点的引用指向 pre.random 。

本题难点,如何去构建各结点的random引用指向(普通的遍历复制无法解决)

class Solution:
    def copyRandomList(self, head: 'Node') -> 'Node':
        cur = head
        dum = pre = Node(0)
        while cur:
            node = Node(cur.val) # 复制节点 cur
            pre.next = node      # 新链表的 前驱节点 -> 当前节点
            # pre.random = '???' # 新链表的 「 前驱节点 -> 当前节点 」 无法确定
            cur = cur.next       # 遍历下一节点
            pre = node           # 保存当前新节点
        return dum.next

在这里插入图片描述
方法①:哈希表

利用哈希表的查询特点,考虑构建 原链表节点新链表对应节点 的键值对映射关系,再遍历构建新链表各节点的 next 和 random 引用指向即可。

在这里插入图片描述

# -*- coding:utf-8 -*-
# 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
        if not (pHead):
            return
        dic = {} #创建哈希表
        # 复制各个结点,并建立“原结点->新结点”的 Map 映射,添加键值对
        cur = pHead
        while cur:
            dic[cur] = RandomListNode(cur.label)  #这里注意定义新的结点的函数名是啥!!和对应方法名称!!
            cur = cur.next
        cur = pHead
        # 构建新结点的 next 和 random 指向
        while cur:
            dic[cur].next = dic.get(cur.next)
            dic[cur].random = dic.get(cur.random)
            cur = cur.next
        # 返回新链表的头结点
        return dic[pHead]

1
在这里插入图片描述
3
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

注意: python字典中的get方法

python字典的get方法会返回指定键的值,dict.get(‘键’),返回“键”对应的“值”,如果键不在字典中则返回默认值None。

方法②:拼接+拆分

思路:

在这里插入图片描述

重点是如何构建random指向,思路是 cur.next.random = cur.random.next
(这里cur指向原链表的一个结点)

思路图:
1
在这里插入图片描述
2
在这里插入图片描述
3
在这里插入图片描述
4
在这里插入图片描述
5
在这里插入图片描述
6
在这里插入图片描述
7
在这里插入图片描述

"""
# Definition for a Node.
class Node:
    def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
        self.val = int(x)
        self.next = next
        self.random = random
"""
class Solution:
    def copyRandomList(self, head: 'Node') -> 'Node':
        if not head: return #排掉空结点
        # 1.进行结点复制
        cur = head
        while cur:
            tmp = Node(cur.val)
            tmp.next = cur.next
            cur.next = tmp
            cur = cur.next.next #第一遍打的时候忘记写了。。
        # 2.进行random的记录,虽然原来的random不能直接用,但是random.next就是和random一模一样值的结点,直接用(存在的话)
        cur = head
        while cur:
            if cur.random:
                cur.next.random = cur.random.next
            ''' 省略的原因是本来默认就是none
            else: 
                cur.next.random = None 
            '''
            cur = cur.next.next
        # 3.全部复制好后进行拆分链表
        cur = head 
        res = ans = head.next #res是留下来记录复制好的整个链表的,ans进行遍历
        while ans.next: #注意判断条件到底是谁,ans在前面,所以条件就是ans是倒数最后一个结点
            cur.next = cur.next.next
            ans.next = ans.next.next
            cur = cur.next
            ans = ans.next
        cur.next = None # 新的链表用的是原来的none,所以还给原本的链表补一个none
        return res

写的时候有点疑惑判断问题,其实就是被两条链表公用一个none影响了,在循环结束之后补上就好了。

注意:

循环的进行语句别忘记写,cur = cur.next…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值