初学python的我开始Leetcode题-7.3

提示:100道LeetCode热题-7.3主要是链表相关,包括四题:删除链表的倒数第 N 个结点、两两交换链表中的节点、K 个一组翻转链表、随机链表的复制。由于初学,所以我的代码部分仅供参考。


目录

前言

题目1:删除链表的倒数第 N 个结点

1.题目要求:

2.结果代码:

题目2:两两交换链表中的节点

1.题目要求:

2.结果代码:

题目3:K 个一组翻转链表

1.题目要求:

2.结果代码:

题目4:随机链表的复制

1.题目要求:

2.结果代码:

总结


前言

截取的这四题有助于对链表节点等问题又更深入的认识,后面还会更新有关链表排序问题相关题目,大家有空可以看看o(* ̄▽ ̄*)ブ


提示:以下是本篇文章正文内容,下面结果代码仅供参考

题目1:删除链表的倒数第 N 个结点

1.题目要求:

题目如下:

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

示例 1:

输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

示例 2:

输入:head = [1], n = 1
输出:[]

示例 3:

输入:head = [1,2], n = 1
输出:[1]

提示:

  • 链表中结点的数目为 sz
  • 1 <= sz <= 30
  • 0 <= Node.val <= 100
  • 1 <= n <= sz

代码框架已经提供如下:

# Definition for singly-linked list.

# class ListNode(object):

#     def __init__(self, val=0, next=None):

#         self.val = val

#         self.next = next

class Solution(object):

    def removeNthFromEnd(self, head, n):

        """

        :type head: Optional[ListNode]

        :type n: int

        :rtype: Optional[ListNode]

        """

2.结果代码:

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

class Solution(object):
    def removeNthFromEnd(self, head, n):
        """
        :type head: Optional[ListNode]
        :type n: int
        :rtype: Optional[ListNode]
        """
        # 创建一个虚拟头节点,方便处理删除头节点的情况
        dummy = ListNode(0)
        dummy.next = head
        
        # 初始化两个指针
        p1 = dummy
        p2 = dummy
        
        # 让 p1 先移动 n 步
        for _ in range(n):
            p1 = p1.next
        
        # 同时移动 p1 和 p2,直到 p1 到达链表末尾
        while p1.next:
            p1 = p1.next
            p2 = p2.next
        
        # 删除 p2 的下一个节点
        p2.next = p2.next.next
        
        # 返回新的头节点
        return dummy.next

说明:

1)这题使用双指针法。具体思路是:

  1. 使用两个指针 p1 和 p2,初始时都指向头节点。

  2. 先让 p1 向前移动 n 步,这样 p1 和 p2 之间的距离就是 n。

  3. 然后同时移动 p1 和 p2,当 p1 到达链表末尾时,p2 的下一个节点就是需要删除的节点。

  4. 修改指针,跳过需要删除的节点。

2)这题最大的提升点是方法只需要一次遍历链表,时间复杂度为 O(L),其中 L 是链表的长度。

题目2:两两交换链表中的节点

1.题目要求:

题目如下:

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

示例 1:

输入:head = [1,2,3,4]
输出:[2,1,4,3]

示例 2:

输入:head = []
输出:[]

示例 3:

输入:head = [1]
输出:[1]

提示:

  • 链表中节点的数目在范围 [0, 100] 内
  • 0 <= Node.val <= 100

代码框架已经提供如下:

# Definition for singly-linked list.

# class ListNode(object):

#     def __init__(self, val=0, next=None):

#         self.val = val

#         self.next = next

class Solution(object):

    def swapPairs(self, head):

        """

        :type head: Optional[ListNode]

        :rtype: Optional[ListNode]

        """

2.结果代码:

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

class Solution(object):
    def swapPairs(self, head):
        """
        :type head: Optional[ListNode]
        :rtype: Optional[ListNode]
        """
        # 创建一个虚拟头节点,方便处理头节点的交换
        dummy = ListNode(0)
        dummy.next = head
        prev = dummy
        
        # 当链表中还有至少两个节点时进行交换
        while prev.next and prev.next.next:
            # 获取要交换的两个节点
            first = prev.next
            second = prev.next.next
            
            # 交换两个节点
            prev.next = second
            first.next = second.next
            second.next = first
            
            # 更新 prev 指针,继续处理下一对节点
            prev = first
        
        # 返回新的头节点
        return dummy.next

说明:我的代码解题思路

  1. 特殊情况处理

    • 如果链表为空(headNone),直接返回 None

    • 如果链表只有一个节点,直接返回该节点。

  2. 两两交换节点

    • 使用一个虚拟头节点(dummy),它的 next 指向链表的头节点。这样可以方便处理头节点的交换。

    • 使用一个指针(prev)来记录当前交换的前一个节点。

    • 每次交换两个节点后,更新指针,继续处理下一对节点。

  3. 终止条件

    • 当链表中剩余节点不足两个时,停止交换。

  • 遍历链表一次,时间复杂度为 O(L),其中 L 是链表的长度。

  • 空间复杂度为 O(1),因为只使用了常数级别的额外空间。

题目3:K 个一组翻转链表

1.题目要求:

题目如下:

给你链表的头节点 head ,每 k 个节点一组进行翻转,请你返回修改后的链表。

k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

示例 1:

输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]

示例 2:

输入:head = [1,2,3,4,5], k = 3
输出:[3,2,1,4,5]

提示:
  • 链表中的节点数目为 n
  • 1 <= k <= n <= 5000
  • 0 <= Node.val <= 1000

进阶:你可以设计一个只用 O(1) 额外内存空间的算法解决此问题吗?

代码框架已经提供如下:

# Definition for singly-linked list.

# class ListNode(object):

#     def __init__(self, val=0, next=None):

#         self.val = val

#         self.next = next

class Solution(object):

    def reverseKGroup(self, head, k):

        """

        :type head: Optional[ListNode]

        :type k: int

        :rtype: Optional[ListNode]

        """

2.结果代码:

# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution(object):
    def reverseKGroup(self, head, k):
        """
        :type head: Optional[ListNode]
        :type k: int
        :rtype: Optional[ListNode]
        """
        # 统计链表的总节点数
        n = 0
        cur = head
        while cur:  # 遍历链表,统计节点个数
            n += 1
            cur = cur.next

        # 创建哨兵节点,方便处理链表头节点的反转
        dummy = ListNode(next=head)  
        p0 = dummy  # 初始化指针 p0,指向哨兵节点

        # 当链表剩余节点数不少于 k 时,进行反转操作
        while n >= k:
            n -= k  # 每次处理 k 个节点后,剩余节点数减 k

            # 反转当前组的 k 个节点
            pre = None  # 初始化 pre 指针,用于反转链表
            cur = p0.next  # cur 指向当前组的第一个节点
            for _ in range(k):  # 反转 k 个节点
                t = cur.next  # 保存 cur 的下一个节点
                cur.next = pre  # 将 cur 的 next 指向 pre,实现反转
                pre = cur  # 更新 pre 指针
                cur = t  # 更新 cur 指针

            # 重新连接链表
            t = p0.next  # t 指向当前组的第一个节点(反转后的最后一个节点)
            p0.next.next = cur  # 将反转后的最后一个节点的 next 指向下一组的第一个节点
            p0.next = pre  # 将 p0 的 next 指向反转后的第一个节点
            p0 = t  # 更新 p0 指针,指向下一组的第一个节点

        # 返回反转后的链表头节点
        return dummy.next

说明:

1)这题最大的难点有特殊情况的处理:

  • 链表长度不足 k 个节点:如果链表的长度小于 k,则直接返回原链表,不做任何翻转。

  • 链表长度不是 k 的整数倍:最后剩余的节点不足 k 个时,需要保持原有顺序,不能进行翻转。

  • 翻转头节点:当翻转操作涉及头节点时,需要特别处理,因为头节点的翻转会改变链表的头指针。

2)这题最大的提升点:

  • 时间复杂度:O(L),其中 L 是链表的长度。虽然代码中有两层循环,但每个节点只被访问一次。

  • 空间复杂度:O(1),只使用了常数级别的额外空间。

题目4:随机链表的复制

1.题目要求:

题目如下:

给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。

构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 

例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。

返回复制链表的头节点。

用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:

  • val:一个表示 Node.val 的整数。
  • random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为  null 。

你的代码  接受原链表的头节点 head 作为传入参数。

示例 1:

输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]

示例 2:

输入:head = [[1,1],[2,1]]
输出:[[1,1],[2,1]]

示例 3:

输入:head = [[3,null],[3,0],[3,null]]
输出:[[3,null],[3,0],[3,null]]

提示:

  • 0 <= n <= 1000
  • -104 <= Node.val <= 104
  • Node.random 为 null 或指向链表中的节点。

代码框架已经提供如下:

"""

# Definition for a Node.

class Node:

    def __init__(self, x, next=None, random=None):

        self.val = int(x)

        self.next = next

        self.random = random

"""

class Solution(object):

    def copyRandomList(self, head):

        """

        :type head: Node

        :rtype: Node

        """

2.结果代码:

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

class Solution:
    def copyRandomList(self, head):
        if not head:
            return None

        # 第一步:复制每个节点并插入到原节点之后
        current = head
        while current:
            new_node = Node(current.val, current.next, None)
            current.next = new_node
            current = new_node.next

        # 第二步:设置随机指针
        current = head
        while current:
            if current.random:
                current.next.random = current.random.next
            current = current.next.next

        # 第三步:拆分链表
        old_list = head
        new_list = head.next
        new_head = head.next

        while old_list:
            old_list.next = old_list.next.next
            if new_list.next:
                new_list.next = new_list.next.next
            old_list = old_list.next
            new_list = new_list.next

        return new_head

说明:

1)这题的难点:

  1. 随机指针的处理

    • 随机指针可能指向链表中的任意节点,甚至可能形成环。直接复制节点时,无法直接确定随机指针的目标节点。

    • 需要一种方法来记录原节点与新节点之间的映射关系,以便正确设置随机指针。

  2. 避免重复创建节点

    • 如果直接遍历链表并创建新节点,可能会因为随机指针的存在而多次创建同一个节点,导致错误。

  3. 高效实现

    • 需要一种高效的方法来完成深拷贝,避免多次遍历链表。

因此,可以使用 三步法 来解决这个问题,这种方法的时间复杂度为 O(n),空间复杂度为 O(1):

第一步:复制每个节点并插入到原节点之后

  • 遍历链表,对于每个节点 Node,创建一个新节点 Node’,并将 Node’ 插入到 Node 和 Node.next 之间。

  • 例如,原链表为 A→B→C,复制后变为 A→A′→B→B′→C→C′。

第二步:设置随机指针

  • 再次遍历链表,对于每个原节点 Node,其新节点 Node’ 的随机指针可以通过 Node.random.next 来设置。

  • 例如,如果 Node.random 指向 Node2,那么 Node’.random 应指向 Node2.next。

第三步:拆分链表

  • 最后,将链表拆分为两个独立的链表:原链表和新链表。

  • 遍历链表,恢复原链表的 next 指针,并提取新链表的节点。

2)这题最大的提升点:

  • 时间复杂度:O(n),其中 n 是链表的长度。每个节点只被访问三次(复制、设置随机指针、拆分)。

  • 空间复杂度:O(1),除了新链表外,只使用了常数级别的额外空间。


总结

针对链表的四种题型进行了学习,了解了部分有关链表与python的相关知识,大家加油!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值