[Leetcode] [Tutorial] 链表


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

2. 两数相加

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

示例:

输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.

Solution

class Solution:
    def addTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
        dummy = ListNode(-1)
        curr = dummy
        carry = 0
        while l1 or l2:
            val1 = l1.val if l1 else 0
            val2 = l2.val if l2 else 0
            carry, val = divmod(val1 + val2 + carry, 10)
            curr.next = ListNode(val)
            if l1:
                l1 = l1.next
            if l2:
                l2 = l2.next
            curr = curr.next
        if carry:
            curr.next = ListNode(carry)
        return dummy.next

21. 合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

示例:
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]

Solution

class Solution:
    def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
        dummy = ListNode(-1)
        curr = dummy

        while list1 and list2:
            if list1.val <= list2.val:
                curr.next = list1
                list1 = list1.next
            else:
                curr.next = list2
                list2 = list2.next
            curr = curr.next

        if list1:
            curr.next = list1
        elif list2:
            curr.next = list2

        return dummy.next

19. 删除链表的倒数第 N 个结点

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

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

Solution

我们可以使用栈解决这个问题,链表中的每个节点都入栈,然后我们再出栈 n 次,那么此时栈顶元素就是我们要删除的节点的前一个节点。这是因为栈是一种后进先出的数据结构,出栈的顺序刚好与入栈的顺序相反,因此出栈的顺序就是链表节点的倒序。

在删除节点后,我们还需要考虑一个问题,如果我们删除的是头结点,那么我们需要重新确定新的头结点。为了解决这个问题,我们可以在最开始添加一个哑节点。

class Solution:
    def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
        dummy = ListNode(0, head)
        stack = []
        cur = dummy
        while cur:
            stack.append(cur)
            cur = cur.next

        for _ in range(n):
            stack.pop()

        prev = stack[-1]
        prev.next = prev.next.next
        return dummy.next

我们还可以使用快慢指针法来解决这个问题。维护两个指针,一个指针先前进n步,然后两个指针同时前进,直到快指针到达链表的末尾。这时候慢指针指向的就是我们需要删除的节点。

class Solution:
    def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
        dummy = ListNode(0)
        dummy.next = head
        fast = slow = dummy
        # 快指针先前进n步
        for _ in range(n + 1):
            fast = fast.next
        # 快慢指针同时前进,直到快指针到达链表末尾
        while fast:
            fast = fast.next
            slow = slow.next
        # 删除慢指针的下一个节点
        slow.next = slow.next.next
        return dummy.next

160. 相交链表

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

图示两个链表在节点 c1 开始相交:


题目数据 保证 整个链式结构中不存在环。

示例:

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3

输出:Intersected at ‘8’

Solution

class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
        if not headA or not headB:
            return None
        
        nodeA, nodeB = headA, headB
        while nodeA != nodeB:
            nodeA = nodeA.next if nodeA else headB
            nodeB = nodeB.next if nodeB else headA
            
        return nodeA

142. 环形链表 II

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

示例:

输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

Solution

假设从链表的起始位置到环的起始位置的距离为 A,环的起始位置到慢指针和快指针第一次相遇的位置的距离为 B,环的长度为 C。

当慢指针和快指针第一次相遇时,慢指针已经走了 A+B 步,而快指针已经走了 A+B+kC 步,其中 k 是快指针在环中多走的圈数。

因为快指针的速度是慢指针的两倍,所以 2(A+B) = A+B+kC。从这个等式中,我们可以得出 A = kC - B。这意味着从头节点到环的起始节点的距离与从第一次相遇位置到环的起始位置的距离相同。

class Solution:
    def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if not head or not head.next:
            return None

        slow, fast = head, head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
            if slow == fast:
                break
        
        if not fast or not fast.next:
            return None

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

206. 反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

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

Solution

class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if not head:
            return None

        prev = None
        curr = head
        while curr:
            temp_next = curr.next
            curr.next = prev
            prev = curr
            curr = temp_next

        return prev

在这个代码中,我们首先检查 head 是否为 None 或者 head.next 是否为 None ,如果是的话,我们直接返回 head,因为空链表或只有一个元素的链表反转后还是它们自己。

接着,我们初始化 prev 为 None, curr 为 head,然后开始循环。如果 prev 为 head, curr 为 head.next,会使原始链表的头节点(即反转后链表的尾节点)的 next 属性仍然指向原始链表的第二个节点。

234. 回文链表

给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。

示例:
输入:head = [1,2,2,1]
输出:true

Solution

一种直接的方法是使用一个栈存储链表的每个元素,然后比较栈的弹出序列和链表的遍历序列是否相同。

要判断一个链表是否为回文链表,我们可以首先找到链表的中点,然后反转链表的后半部分,然后比较链表的前半部分和后半部分是否相同。如果相同,那么链表就是回文的。

我们可以使用快慢指针法找到链表的中点。我们创建两个指针,一个快指针每次移动两步,一个慢指针每次移动一步。当快指针到达链表的尾部时,慢指针就在链表的中点。

class Solution:
    def isPalindrome(self, head: Optional[ListNode]) -> bool:
        if not head or not head.next:
            return True

        # Find the middle of the list
        slow, fast = head, head
        while fast.next and fast.next.next:
            slow = slow.next
            fast = fast.next.next

        # Reverse the second half of the list
        prev, curr = None, slow.next
        while curr:
            next_temp = curr.next
            curr.next = prev
            prev = curr
            curr = next_temp

        # Compare the first half and the second half
        first, second = head, prev
        while second:
            if first.val != second.val:
                return False
            first = first.next
            second = second.next

        return True

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

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

示例:

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

Solution

可以通过递归的方式实现两两交换链表中的节点。

class Solution:
    def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if not head or not head.next:
            return head
        # nodes in current pair
        first_node = head
        second_node = head.next
        # swapping
        first_node.next = self.swapPairs(second_node.next)
        second_node.next = first_node
        # now head is second node
        return second_node

我们还可以采用迭代的方式来解决这个问题。

class Solution:
    def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if not head or not head.next:
            return head

        dummy = ListNode(-1)
        dummy.next = head
        prev = dummy
        while prev.next and prev.next.next:
            first_node = prev.next
            second_node = prev.next.next

            prev.next = second_node
            first_node.next = second_node.next
            second_node.next = first_node

            prev = first_node

        return dummy.next

25. K 个一组翻转链表

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

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

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

示例:

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

Solution

在“两两交换”问题中,我们是两个节点作为一组进行交换,而现在我们是k个节点作为一组进行交换。因此,我们可以参考“两两交换”问题的递归的方案来解决这个问题。

class Solution:
    def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
        # 首先,我们需要检查是否有足够的k个节点可以反转
        count = 0
        node = head
        while node and count < k:
            node = node.next
            count += 1
        if count < k:
            return head
        
        prev = None
        curr = head
        for _ in range(k):
            temp_next = curr.next
            curr.next = prev
            prev = curr
            curr = temp_next
        # 然后我们递归地反转剩下的节点,并将反转后的节点连接到已经反转的节点上
        head.next = self.reverseKGroup(curr, k)
        # 最后我们返回反转后的头节点
        return prev

我们还可以采用迭代的方式来解决这个问题。

class Solution:
    def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
        dummy = ListNode(0)
        dummy.next = head
        groupPrev = dummy
        while True:
            kth = self.getKth(groupPrev, k)
            if not kth:
                break
            groupNext = kth.next
            prev, curr = kth.next, groupPrev.next
            while curr != groupNext:
                nextNode = curr.next
                curr.next = prev
                prev, curr = curr, nextNode
            nextGroupPrev = groupPrev.next
            groupPrev.next = prev
            groupPrev = nextGroupPrev
        return dummy.next

    def getKth(self, curr: ListNode, k: int) -> ListNode:
        while curr and k > 0:
            curr = curr.next
            k -= 1
        return curr

328. 奇偶链表

给定单链表的头节点 head ,将所有索引为奇数的节点和索引为偶数的节点分别组合在一起,然后返回重新排序的列表。

第一个节点的索引被认为是 奇数 , 第二个节点的索引为 偶数 ,以此类推。

请注意,偶数组和奇数组内部的相对顺序应该与输入时保持一致。

示例:

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

Solution

138. 复制带随机指针的链表

给你一个长度为 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 。

# 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

示例:

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

Solution

class Solution:
    def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]':
        if not head:
            return None

        mapping = {
    
    }
        curr = head
        while curr:
            mapping[curr] = Node(curr.val)
            curr = curr.next

        curr = head
        while curr:
            if curr.next:
                mapping[curr].next = mapping[curr.next]
            if curr.random:
                mapping[curr].random = mapping[curr.random]
            curr = curr.next
        
        return mapping[head]

有一个更巧妙且高效的方法来解决这个问题,即在原链表的每个节点后面直接插入它的复制,然后修复random指针,最后再拆分原链表和复制链表。

148. 排序链表

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。

Solution

我们可以把归并排序的思想应用到链表排序问题上:

  • 分裂:我们需要找到链表的中点,将链表分裂成两个较小的链表。这可以通过快慢指针的方法来实现,快指针每次移动两步,慢指针每次移动一步,当快指针到达链表尾部时,慢指针正好在链表的中点。

  • 递归排序:我们将两个分裂后的链表看成是两个新的待排序链表,对它们分别进行排序。这个过程是递归的,递归的基本情况是链表长度为1(只有一个元素)或0(没有元素),这时链表已经是有序的,直接返回即可。

  • 合并:我们需要将两个已排序的链表合并成一个大的有序链表。合并的过程也可以看成是一种双指针操作:我们每次比较两个链表的头节点,将较小的节点添加到结果链表中,然后将对应的指针向前移动一步,直到两个链表中的节点都被处理完。

class Solution:
    def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        # Base case
        if not head or not head.next:
            return head
        # Split the list into two halves
        slow, fast = head, head.next
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
        # Recursively sort the two halves
        second = slow.next
        slow.next = None
        left = self.sortList(head)
        right = self.sortList(second)
        # Merge the sorted halves
        return self.merge(left, right)

    def merge(self, left, right):
        dummy = ListNode(0)
        curr = dummy
        while left and right:
            if left.val < right.val:
                curr.next = left
                left = left.next
            else:
                curr.next = right
                right = right.next
            curr = curr.next
        curr.next = left if left else right
        return dummy.next

23. 合并 K 个升序链表

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

Solution

归并排序给了我们一种启示:我们可以首先将k个链表分为两个组,每个组包含k/2个链表,然后对每个组进行合并操作,这样我们就把一个规模为k的问题分解为了两个规模为k/2的问题。然后我们可以继续将每个组进一步分解,直到每个组只包含一个链表,这时就可以直接进行合并操作了。

class Solution:
    def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
        if not lists:
            return None
        elif len(lists) == 1:
            return lists[0]
        mid = len(lists) // 2
        left = self.mergeKLists(lists[:mid])
        right = self.mergeKLists(lists[mid:])
        return self.mergeTwoLists(left, right)

    def mergeTwoLists(self, l1: Optional[ListNode], l2: Optional[ListNode]) -> Optional[ListNode]:
        dummy = ListNode(0)
        curr = dummy
        while l1 and l2:
            if l1.val < l2.val:
                curr.next = l1
                l1 = l1.next
            else:
                curr.next = l2
                l2 = l2.next
            curr = curr.next
        curr.next = l1 if l1 else l2
        return dummy.next

猜你喜欢

转载自blog.csdn.net/weixin_45427144/article/details/131475282