【chap4-链表】用Python3刷《代码随想录》

通过指针串联在一起的线性结构,每一个节点由两部分组成,一个是数据域data,另一个是指针域next(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针)

链接的入口点称为链表的头节点head

【chap4-链表】用Python3刷《代码随想录》_第1张图片 单链表

1、链表的类型

三种类型:单链表、双链表、循环链表 

【chap4-链表】用Python3刷《代码随想录》_第2张图片 双链表

【chap4-链表】用Python3刷《代码随想录》_第3张图片 循环链表

2、链表的存储方式

数组在内存中是连续分布的,但链表在内存中不是连续分布的,链表是通过指针域的指针来链接内存中各个节点的。即:链表的节点在内存中是分散存储的,通过指针连在一起

3、链表的定义

# Python定义链表的节点
class ListNode:
    def __init__(self, val, next=None):
        self.val = val
        self.next = next

4、链表的操作

(1)删除节点:

【chap4-链表】用Python3刷《代码随想录》_第4张图片

此时节点D依然存留在内存中,只不过从链表中被移除了而已。Python有自己的内存回收机制,不用手动释放

(2 )添加节点:

【chap4-链表】用Python3刷《代码随想录》_第5张图片

链表的添加和删除都是时间复杂度为 O(1) 的操作,不会影响其他节点。但是,删除或者添加某个节点的前提是找到操作节点的前一个节点,而查找前一个节点的时间复杂度是 O(n)

5、性能分析

【chap4-链表】用Python3刷《代码随想录》_第6张图片

在定义数组的时候,长度是固定的,如果想改动数组的长度,则需要重新定义一个新的数组; 

链表的长度可以是不固定的,并且可以动态增删,适合数据量不固定、频繁增删、较少查询的场景


203. 移除链表元素 

203. 移除链表元素

删除元素就是让节点的 next 指针直接指向下一个节点的下一个节点,因为单链表的特殊性,所以需要找到操作节点的前一个节点

两种链表操作方式:

  • 直接使用原来的链表执行删除操作:删除头节点和删除其他节点的操作是不一样的,因为链表的其他节点都是通过前一个节点来删除当前节点的,而头节点没有前一个节点,只要将头节点向后移动一位即可
  • 设置一个虚拟头节点再执行删除操作:设置一个虚拟头节点dummy_head,这样原链表的所有节点都可以按照统一的方式删除了。新的头节点是dummy_head.next
# 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]:
        # 设置一个虚拟头节点
        dummy_head = ListNode(next=head)
        # 设置指针位置
        cur = dummy_head
        while cur.next != None:
            if cur.next.val == val:
                cur.next = cur.next.next
            else:
                cur = cur.next
        return dummy_head.next

707. 设计链表

707. 设计链表

这里以单链表为例。注意,在定义链表之前需要先定义链表节点的结构体

【chap4-链表】用Python3刷《代码随想录》_第7张图片

(1)获取下标为 index 的节点的值:注意 cur 初始化为 dummy_head.next,也就是真正的head

(2)头部插入节点:注意操作顺序,十分重要!!!还要记得 size 要 +1

(3)尾部插入节点:记得 size 要 +1

(4)第n个节点前插入节点:操作第n个点,第n个点一定是cur_next,这样才能用 cur 去控制第n个点是增加还是删除。注意操作顺序,十分重要!!!以及记得 size 要 +1

(5)删除下标为 index 的节点:删除下标为 index 的节点(cur.next),必须要知道它的前一个节点(cur),让它的前一个节点指向该节点的后一个节点,才能删除该节点。以及记得 size 要 -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(0)
        self.size = 0


    def get(self, index: int) -> int:
        if index<0 or index>self.size-1:
            return -1
        cur = self.dummy_head.next
        while index:
            cur = cur.next
            index-=1
        return cur.val


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


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


    def addAtIndex(self, index: int, val: int) -> None:
        if index>self.size:
            return
        else:
            new_node = ListNode(val)
            cur = self.dummy_head
            while index:
                cur = cur.next
                index-=1
            new_node.next = cur.next
            cur.next = new_node
            self.size+=1


    def deleteAtIndex(self, index: int) -> None:
        if index<0 or index>self.size-1:
            return
        cur = self.dummy_head
        while index:
            cur = cur.next
            index-=1
        cur.next = cur.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)

206. 反转链表

206. 反转链表

法1:双指针法

  • cur 先指向 head,这是第一个指针
  • 第二个指针,就是反转之后,cur指向它的后面了,定义为 pre(其实就是 cur 的前一位),初始化为空指针
  • 遍历链表,直到 cur 指向空指针结束
  • 遍历时,需要第三个指针 tmp,在没反转之前,保存 cur 的下一个指针,再反转
  • 最后新链表的头节点就是 pre
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
        cur = head
        pre = None 
        while cur != None:
            tmp = cur.next
            cur.next = pre  # 改变方向
            # 指针往后遍历
            pre = cur
            cur = tmp
        return pre

法2:递归法

同样是当 cur 为空的时候循环结束,不断将 cur 指向 pre

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:

        def revserse(cur, pre):
            if cur == None:
                return pre
            tmp = cur.next
            cur.next = pre
            return revserse(tmp,cur)
        
        return revserse(head, None)

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

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

【chap4-链表】用Python3刷《代码随想录》_第8张图片

几个重点: 

(1)操作指针一定要指向要反转的两个节点的前一个结点,如要交换节点1和节点2,此时操作指针应指向dummy_head(节点1的前一个节点)

(2)遍历什么时候结束?    while cur.next != None and cur.next.next != None:

  • 链表节点个数为偶数时,cur.next = null
  • 链表节点个数为奇数时,cur.next.next = null
  • 链表节点个数为0,即空链表时,cur.next = null(此时cur指向虚拟头节点)

(3)交换相邻两个元素时,要注意操作的先后顺序

  • 初始时,cur指向虚拟头节点

【chap4-链表】用Python3刷《代码随想录》_第9张图片

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def swapPairs(self, head: Optional[ListNode]) -> Optional[ListNode]:
        dummy_head = ListNode(next=head)   # 创建虚拟头结点
        cur = dummy_head   # 当前操作指针指向虚拟头结点

        while cur.next != None and cur.next.next != None:
            tmp = cur.next   # 保存节点1
            tmp1 = cur.next.next.next   # 保存节点3
            cur.next = cur.next.next   # 虚拟头结点指向节点2
            cur.next.next = tmp   # 节点2指向节点1
            tmp.next = tmp1   # 节点1指向节点3
            cur = cur.next.next   # 将当前操作指针往后移动2位

        return dummy_head.next   # 返回交换后链表的头节点

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

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

要删某个节点,操作指针要指向该节点的前一个节点

如何找到倒数第N个节点?

  • 虚拟头节点:好处是不需要对操作节点是不是头节点进行特殊的判断,而可以采用统一的方式进行删除   dummy_head = ListNode(next=head)
  • 定义快慢指针,让快指针先移动N步,然后快慢指针再同时移动,直到快指针指向了空节点,此时慢指针就指向了要删的倒数第N个节点
  • 但问题是,要删倒数第N个节点,操作指针要指向该节点的前一个节点,即慢指针应指向倒数第N个节点的前一个节点
  • 故最终:定义快慢指针,让快指针先移动N+1步,然后快慢指针再同时移动,直到快指针指向了空节点,此时慢指针就指向了要删的倒数第N个节点的前一个

【chap4-链表】用Python3刷《代码随想录》_第10张图片

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
        # 定义虚拟头节点
        dummy_head = ListNode(next=head)
        # 定义快慢指针,初始值都为虚拟头节点
        fast = slow = dummy_head

        # 让快指针先走n+1步
        for i in range(n+1):
            fast = fast.next
        # 然后快慢指针再同时走,直到快指针为空
        while fast != None:
            fast = fast.next
            slow = slow.next
        # 此时慢指针指向倒数第n个节点的前一个
        slow.next = slow.next.next
        return dummy_head.next

160. 相交链表

160. 相交链表

【chap4-链表】用Python3刷《代码随想录》_第11张图片

注:交点指引用完全相同,即内存地址完全相同的交点 

若一个指针到达链表的结尾,就切换到另一个链表的头节点继续移动 

LeetCode刷题|python版本|160题|相交链表_哔哩哔哩_bilibili

分析:设第一个公共节点为node,链表A的节点数量为a,链表B的节点数量为b,两链表公共尾部的节点数量为c,则有 

  • 头节点 headA 到 node 前,共有 a - c 个节点
  • 头节点 headB 到 node 前,共有 b - c 个节点

考虑构建两个节点指针 curA、curB,分别指向两链表头节点 headA、headB,做如下操作:

  • 指针 curA 先遍历完链表A,再开始遍历链表B,当走到 node 时,共走步数为:a+(b-c)
  • 指针 curB 先遍历完链表B,再开始遍历链表A,当走到 node 时,共走步数为:b+(a-c)

可以发现它们走的步数相同,所以此时指针 curA 和 curB 重合,且有两种情况:

  • 若两链表有公共尾部(即c>0):指针 curA 和 curB 同时指向第一个公共节点node
  • 若两链表无公共尾部(即c=0):指针 curA 和 curB 同时指向null

故返回 curA 和 curB 任一指针即可

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

class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
        # 定义两指针,分别指向两链表的头节点
        curA = headA
        curB = headB

        # 两指针不相等时就同时向后移动
        while curA != curB:
            curA = curA.next if curA else headB   # 若curA走到空,跳到B
            curB = curB.next if curB else headA
        # 当两指针相等时返回任意一个指针即可
        return curA

算法复杂度:

  • 由于两指针最多遍历两个链表,故时间复杂度为O(m+n),其中m和n为两链表的长度
  • 由于不需要开辟额外空间,故空间复杂度为O(1)

142. 环形链表II 

142. 环形链表 II

这题有两小问:

  • 判断链表是否有环:快慢指针都从头节点出发,快指针每次走2步,慢指针每次走1步,若能相遇则有环
  • 寻找环的入口:在相遇节点处定义一个指针index1,在头节点处定一个指针index2,让index1和index2同时移动,每次移动一个节点,那么它们相遇的地方就是环的入口节点
# 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, slow = head, head
        while fast != None and fast.next != None:    # 因为fast每次走两步
            fast = fast.next.next
            slow = slow.next
            if fast == slow:   # 若快慢指针相遇
                index1 = fast    # 相遇点
                index2 = head    # 头节点
                while index1 != index2:    # 两者相遇时终止
                    index1 = index1.next
                    index2 = index2.next
                return index1
        return None

你可能感兴趣的:(LeetCode,链表,数据结构)