leetcode刷题-链表总结(python)

一、链表基础

1.链表定义

链表是一系列节点组成的元素集合。每个节点包含两个部分,数据域item和指向下一个节点的指针next。通过节点之间的互相连接最终串成一个链表。

单链表,链表的入口节点称为头结点head,最后一个节点指向None。

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

双链表,双链表的每个节点有两个指针,一个指向前一个一个指向后一个节点。

class ListNode:
    def __init__(self, val=0, prev=None, next=None):
        self.val = val
        self.prev = prev
        self.next = next

循环链表,链表首尾相连

 2.链表的插入和删除

插入,时间复杂度O(1)

p.next = curNode.next
curNode.next = p

 删除,时间复杂度O(1)

curNode.next = curNode.next.next

 

二、移除链表元素

力扣题目链接

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

删除头节点和删除中间的节点操作不一样 ,为了统一方便操作,可以引入虚拟头结点。

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
        # 设置一个虚拟头结点,避免单独讨论头结点是val的情况
        dummy_head = ListNode(next = head)
        current = dummy_head   # current和dummy_head都指向同一个链表结构
        while current.next:    # 在这个循环的过程中修改的该链表结构
            if current.next.val == val:
                current.next = current.next.next
            else:
                current = current.next
        return dummy_head.next    # 最后返回这个是因为current已经向后移动了,但dummy_head还是指向的该被修改后的链表结构

三、设计链表

力扣题目链接

设置虚拟头结点,方便操作,虚拟头结点下标可以认为是-1。每次查询插入删减要记得做判断。下面给出用单链表的做法。

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

class MyLinkedList:

    def __init__(self):
        self.dummy_head = ListNode()  # 创建一个虚拟头结点,虚拟头结点永远是虚拟头结点,其下标认为是-1
        self.size = 0     # 目前的长度是0

    def get(self, index: int) -> int:
        if index < 0 or index >= self.size:
            return -1
        current = self.dummy_head
        for _ in range(index):
            current = current.next
        return current.next.val

    def addAtHead(self, val: int) -> None:
        self.dummy_head.next = ListNode(val, self.dummy_head.next)
        self.size += 1

    def addAtTail(self, val: int) -> None:
        current = self.dummy_head
        while current.next:
            current = current.next
        current.next = ListNode(val)
        self.size += 1

    def addAtIndex(self, index: int, val: int) -> None:
        if index > self.size or index < 0:
            return
        else:
            current = self.dummy_head
            for _ in range(index):
                current = current.next
            current.next = ListNode(val,current.next)
            self.size += 1

    def deleteAtIndex(self, index: int) -> None:
        if index < 0 or index >= self.size:
            return
        else:
            current = self.dummy_head
            for _ in range(index):
                current = current.next
            current.next = current.next.next
            self.size -= 1


# Your MyLinkedList object will be instantiated and called as such:
# obj = MyLinkedList()
# param_1 = obj.get(index)
# obj.addAtHead(val)
# obj.addAtTail(val)
# obj.addAtIndex(index,val)
# obj.deleteAtIndex(index)

四、反转链表

力扣题目链接

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

 设置一个cur指针,一个pre指针。持续向后移动实现链表反转。

class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        cur = head
        pre = None    # 反转之后头结点前面得有一个None
        while cur:
            tmp = cur.next
            cur.next = pre
            pre = cur
            cur = tmp
        return pre 

五、两两交换链表中的节点

力扣题目链接 

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

使用虚拟头结点,两两交换的每一次分为三个步骤,持续循环就行。

 

class Solution:
    def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
        dummy_head = ListNode(0,head)
        cur = dummy_head
        while cur.next and cur.next.next:   # 这里就考虑了是奇数的情况了
            tmp = cur.next
            cur.next = cur.next.next
            tmp1 = cur.next.next
            cur.next.next = tmp
            tmp.next = tmp1
            cur = cur.next.next
        return dummy_head.next

六、删除链表的倒数第N个结点

力扣题目链接 

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

 快慢指针,虚拟头结点。快指针先移动n+1个单位,然后快慢指针一起移动直到快指针指到None,因为快慢指针的相对位置永远不变,所以快指针指到None的时候,慢指针恰好指到倒数第N个结点的前一个节点(不指向该节点是为了删除方便)。

class Solution:
    def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
        # 双指针法(快慢指针),fast先移动n步,然后fast,slow同时移动直到fast指向None,这时slow指向倒数第n个节点
        # 为了方便删除,期望slow指向n的前一个节点,所以fast可以先移动n+1步,再进行同时移动
        dummy_head = ListNode(0,head)
        fast = dummy_head
        slow = dummy_head
        for _ in range(n+1):
            fast = fast.next
        while fast:
            slow = slow.next
            fast = fast.next
        slow.next = slow.next.next
        return dummy_head.next

七、链表相交

力扣题目链接

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

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

分别求出A和B的长度,让长的那个指针移动到和短的对齐,这样两个一起向后走,遇到相同的节点就证明存在交点。

class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        # 注意是指针相同,不是值相同
        lenA = 0
        lenB = 0
        # 求链表A的长度
        cur = headA
        while cur:
            lenA += 1
            cur = cur.next
        # 求链表B的长度
        cur = headB
        while cur:
            lenB += 1
            cur = cur.next
        # 求出长度差,让长的那个往后移动长度差个单位
        # 固定B是长的,A是短的可以减少代码量
        curA = headA
        curB = headB
        if lenA > lenB:
            curA, curB = curB, curA
            lenA, lenB = lenB, lenA    # 固定B是长的那个
        delta = lenB - lenA
        for _ in range(delta):      #对齐了AB的指针
            curB = curB.next
        # A,B同时往后移动,发现相同的指针就停下来返回
        while curA:
            if curA == curB:
                return curB
            else:
                curA = curA.next
                curB = curB.next
        return None

 八、环形链表

力扣题目链接

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

使用快慢指针方法,快指针每次移动两个,慢指针每次移动一个。如果存在环,则快慢指针一定会在环内相遇。为什么是在慢指针走第一圈的过程就肯定会相遇呢?可以自己随便画一个环形图,指定一个环入口位置也就是慢指针的位置,让快指针在任意位置开始追赶慢指针,不管快指针从哪开始,在慢指针走的一圈之内最后一定是下图这样的效果。两个指针再各自走一步就会相遇

判断完一定会相遇后就要判断环的入口在哪,可以看下面的图。

相遇的时候慢指针走了x+y,快指针走了x+y+n(y+z) ,由于快指针的速度是慢指针的二倍,所以

2(x+y)=x+y+n(y+z),消掉x+y,可以得到x+y=n(y+z),x=n(y+z)-y,x=(n-1)(y+z)+z

假设n=1,也就是x等于z,当两个点相遇后让让一个指针指向头结点,一个在相遇的节点,开始向后走,相遇的时候就是环形入口节点。

同理n>1,两个新的指针始终会在入口节点相遇。

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


class Solution:
    def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
        fast = head
        slow = head
        while fast and fast.next:   # 判断fast.next是为了怕列表没有环,但循环中fast要next.next,这样会出现错误
            fast = fast.next.next
            slow = slow.next
            if fast == slow:
                slow = head
                while fast != slow:
                    fast = fast.next
                    slow = slow.next
                return slow
        return None

 

作者:A_小果子

物联沃分享整理
物联沃-IOTWORD物联网 » leetcode刷题-链表总结(python)

发表回复