数据结构与算法----复习Part 6 (链表基础知识)

本系列是算法通关手册LeeCode的学习笔记

算法通关手册(LeetCode) | 算法通关手册(LeetCode) (itcharge.cn)

本系列为自用笔记,如有版权问题,请私聊我删除。

目录

一,链表简介

1. 链表(Linked List)

2. 双向链表(Doubly Linked List)

3. 循环链表(Circular Linked List)

二,链表基本操作

1. 链表的定义与建立

2. 获取链表长度

3. 查找元素

4. 插入元素

5. 改变元素

6. 删除元素

三,总结


一,链表简介

1. 链表(Linked List)

        一种线性表数据结构,使用一组任意的存储单元来存储一组具有相同类型的数据。链表是实现线性表链式存储的基础。

以单链表为例:

        链表通过一组任意的存储单元串联在一起,每个数据元素占用若干存储单元的组合称为一个链节点 。为了将所有节点串起来,每个链节点不仅要存放一个数据元素的值,还要存放一个指出这个数据元素在逻辑关系上的直接后继元素所在链节点的地址,该地址称为后继指针next

        优点:存储空间不用事先分配,一些操作的时间效率远比数组高;

        缺点:指针需要占用存储空间,空间开销大。

2. 双向链表(Doubly Linked List)

        也叫双链表,每个链节点中有两个指针,分别指向直接后继和直接前驱。

        特点:从任意一个节点开始,都可以访问前驱节点和后继节点。

3. 循环链表(Circular Linked List)

        最后一个节点指向头节点,形成一个环。

        特点:从循环链表的任何一个结点出发,都能找到任何其他节点。

二,链表基本操作

1. 链表的定义与建立

        链表是由链节点通过 next 连接而成。

        链节点类(ListNode):使用成员变量 val 表示数据元素的值,指针 next 表示后继指针。

        链表类(LinkedList):使用一个链节点变量 head 来表示链表的头节点。

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

class LinkedList:
    def __init__(self):
        self.head = None

建立线性链表:根据线性表的数据元素动态生成链节点,并以此将其连接到链表中。

        步骤:

        依次获取表中的数据元素;

        每获取一个,就为该数据元素生成一个新节点,将新节点插入到链表尾部;

        插入完毕后,返回第一个链节点的地址。

示例代码:

class LinkedList:
    def __init__(self):
        self.head = None
    def creat(self, data):
        self.head = ListNode(0)
        cur = self.head
        for i in range(len(data)):
            node = ListNode(data[i])
            cur.next = node
            cur = cur.next

         时间复杂度依赖于数据 data 表的长度,所以为 O(n)

2. 获取链表长度

        使用指针变量 cur 顺着链表 next 移动,并使用计数器 count 记录元素个数。

        步骤:

        指针变量指向第一个链节点;

        顺着链节点的 next 遍历链表,每指向一个链节点, count 计数一次;

        cur 指向为空时结束遍历,返回 count

示例代码:

    def length(self):
        cur = self.head
        count = 0
        while cur:
            count += 1
            cur = cur.next
        return count

        时间复杂度为 O(n) 

3. 查找元素

        在链表中查找值为 val 的元素:从头节点 head 开始,沿着链表节点逐一进行查找。若找到则返回节点地址,否则返回 None

        步骤:

        让指针变量 cur 指向链表的第一个链节点;

        顺着链节点的 next 指针遍历链表,如果 cur.val == val ,返回 cur;

        若 cur 为空仍未找到,返回 None。

示例代码:

    def find(self, val):
        cur = self.head
        while cur:
            if val == cur.val:
                return cur
            cur = cur.next
        return None

         时间复杂度为 O(n)

4. 插入元素

        按照链表中插入元素的位置分为:链表头部、链表尾部、链表中间插入元素。

链表头部插入元素:在链表第一个节点之前插入值为 val 的链节点。

        步骤:

        创建一个值为 val 的链节点 node;

        将 node 的后继指针指向链表头节点 head;

        将链表头节点指向 node。

示例代码:

    def insertFront(self, val):
        node = ListNode(val)
        node.next = self.head
        self.head = node

        时间复杂度为 O(1) 

在链表尾部插入元素:在链表最后一个链节点之后插入值为 val 的链节点。

        步骤:

        创建一个值为 val 的链节点 node;

        使用指针 cur 指向链表的头节点 head;

        通过链节点的 next 指针移动 cur 直到 cur.next 为空;

        将 cur.next 指向新的链节点 node。

示例代码:

    def insertRear(self, val):
        node = ListNode(val)
        cur = self.head
        while cur.next:
            cur = cur.next
        cur.next = node

        时间复杂度为 O(n)

链表中间插入元素:在链表第 i 个链节点之前插入值为 val 的链节点。

        步骤:

        使用指针变量 cur 和一个计数器 count;

        沿着链节点的 next 指针遍历链表,并计数;

        遍历到 index - 1 时,创建一个值为 val 的节点 node;

        将 node.next 指向 cur.next;

        将 cur.next 指向 node。

示例代码:

    def insertInside(self, val, index):
        cur = self.head
        count = 0
        while count < index - 1:
            cur = cur.next
            count += 1

        if not cur:
            return 'Error'
        
        node = ListNode(val)
        node.next = cur.next
        cur.next = node

        时间复杂度为 O(n)

5. 改变元素

        将链表中第 i 个元素值改为 val:首先要遍历到第 i 个链节点,然后更改元素值。

        步骤:

        使用指针变量 cur 和计数器 count;

        沿着节点的 next 找到第 i 个链节点;

        更改元素值。

示例代码:

    def change(self, index, val):
        count = 0
        cur = self.head
        while count < index and cur:
            cur = cur.next
            count += 1
        if not cur:
            return 'Error'
        
        cur.val = val

        时间复杂度为 O(n)

6. 删除元素

        删除元素操作与插入元素一样,分为三种。

链表头部删除元素:删除链表的第一个节点。

        步骤

        将 self.head 沿 next 指针右移一步。

示例代码:

    def removeFront(self):
        if self.head:
            self.head = self.head.next

        时间复杂度为 O(1) 

链表尾部删除元素:删除链表末尾最后一个链节点。

        步骤:

        先使用指针变量 cur 沿着 next 移动到倒数第二个链节点;

        将此节点的 next 指针指向 None。

        

示例代码:

    def removeRear(self):
        if not self.head or not self.head.next:
            return 'Error'
        
        cur = self.head
        while cur.next.next:
            cur = cur.next
        cur.next = None

        时间复杂度为 O(n)

链表中间删除元素:删除链表第 i 个链节点。

        步骤:

        先使用指针变量 cur 移动到第 i  - 1 个位置的链节点;

        将 cur 的 next 指针指向第 i 个元素的下一个节点。

示例代码:

    def removeInside(self, index):
        count = 0
        cur = self.head
        
        while cur.next and count < index - 1:
            cur = cur.next
            count += 1
        
        if not cur:
            return 'Error'
        
        del_node = cur.next
        cur.next = del_node.next

        时间复杂度为 O(n)

三,总结

        链表是最基础、最简单的数据结构。是实现线性表的链式存储结构的基础。它使用一组任意的存储单元(可以是连续的,也可以是不连续的),来存储一组具有相同类型的数据。

        链表最大的优点在于可以灵活的添加和删除元素。

在这里给出五道题来练练手 

707. 设计链表

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

class MyLinkedList:

    def __init__(self):
        self.head = ListNode(0)
        self.size = 0


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

    def addAtHead(self, val: int) -> None:
        self.addAtIndex(0, val)


    def addAtTail(self, val: int) -> None:
        self.addAtIndex(self.size, val)

    def addAtIndex(self, index: int, val: int) -> None:
        if index > self.size:
            return
        elif index < 0:
            index = 0

        if 0 <= index <= self.size:
            cur = self.head
            for _ in range(index):
                cur = cur.next
            node = ListNode(val)
            node.next = cur.next
            cur.next = node
            self.size += 1

    def deleteAtIndex(self, index: int) -> None:
        if 0 <= index < self.size:
            cur = self.head
            for _ in range(index):
                cur = cur.next
            
            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)

83. 删除排序链表中的重复元素

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if not head:
            return head
        cur = head
        while cur.next :
            if cur.val == cur.next.val:
                if cur.next.next:
                    cur.next = cur.next.next
                else:
                    cur.next = None
            else:
                cur = cur.next
        return head
        

82. 删除排序链表中的重复元素 II

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
        if not head or not head.next:
            return head
        dummy = ListNode(0)
        dummy.next = head
        pre = dummy
        cur = head
        while cur:
            while cur.next and cur.val == cur.next.val:
                cur = cur.next
            if pre.next == cur:
                pre = pre.next
            else:
                pre.next = cur.next
            cur = cur.next
        return dummy.next

        这里使用了哨兵节点 dummy 用于记录头节点的位置,以保证头节点被删除的情况下,仍可找到链表的开始位置。

206. 反转链表

# 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, pre = head, None
        while cur:
            tmp = cur.next
            cur.next = pre
            pre = cur
            cur = tmp
        return pre

92. 反转链表 II - 力扣(LeetCode)

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseBetween(self, head: Optional[ListNode], left: int, right: int) -> Optional[ListNode]:
        dummy = ListNode()
        dummy.next = head
        p0 = dummy
        for _ in range(left - 1):
            p0 = p0.next
        pre = None
        cur = p0.next
        for _ in range(right - left + 1):
            nxt = cur.next
            cur.next = pre
            pre = cur
            cur = nxt

        p0.next.next = cur
        p0.next = pre
        return dummy.next

算法通关手册(LeetCode) | 算法通关手册(LeetCode)

原文内容在这里,如有侵权,请联系我删除。

  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值