链表

  • 1. 链表简介
    • 1.1 定义及相关原理:
    • 1.2 链表的几种特殊形式
      • 1.2.1 双向链表
      • 1.2.2 循环链表
    • 1.3 链表的基本操作
      • 1.3.1 链表的结构定义
      • 1.3.2 建立一个链表
      • 1.3.3 求链表长度
      • 1.3.4 查找元素
      • 1.3.5 插入元素
      • 1.3.6 改变元素
      • 1.3.7 删除元素
  • 2. 链表相关题目(更新中)
    • 206.反转链表.
    • 203.移除链表元素
    • 328.奇偶链表
    • 234.回文链表

1. 链表简介

1.1 定义及相关原理:

  1. 定义:链表(Linked List):一种线性存储结构。它使用一组任意的存储单元(可以是连续的或不连续的),来存储一组具有相同类型的数据

    链表,是实现线性表的链式存储结构的基础

  2. 存储方式
    由上图可知,链表中的每一个数据都存储在一个链节点中,而为了是每个链节点连接起来,还需要存放一个指出这个数据元素在逻辑关系上的直接后继元素所在链节点地址,该地址被称为后继指针next

  3. 链式存储的优缺点:

    • 优点:存储空间不必事先分配,在需要存储空间的时候可以临时申请,不会造成空间的浪费;一些操作的时间效率远比数组高(插入、移动、删除元素等)。(由链式存储的特点所决定,并不需要连续的空间来存储)
    • 缺点:不仅数据元素本身的数据信息要占用存储空间,指针也需要占用存储空间,链表结构比数组结构的空间开销大。

1.2 链表的几种特殊形式

1.2.1 双向链表

  1. 双向链表(Doubly Linked List):也叫双链表。与一般链表不同的是这里的每个链节点中有两个指针,分别指向直接前驱与直接后继

  2. 达到的效果:使得从任意一个结点开始都可以很方便地访问它的前驱与后继

1.2.2 循环链表

  1. 循环链表(Circular Linked List):与一般链表不同的是它的最后一个链接点指向头节点,形成一个环

  2. 达到的效果:从循环链表的任意一个节点出发都能够找到任意节点

1.3 链表的基本操作

1.3.1 链表的结构定义

链表是由链节点通过 next 链接而构成。所以,链表的结构定义分为两步:

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

  2. 定义链表类(LinkedList):LinkedList类中只有一个链节点变量head来表示链表的头节点

  3. 代码实现:

# 链节点类
class LinkNode:def __ init__(self, val = 0, next = None):self.val = valself.next = next# 链表类
class LinkedList:def __init__(self):self.head = None

1.3.2 建立一个链表

  1. 过程:根据线性表的数据元素动态生成链节点,并依次将其连接到链表中
  2. 步骤:
    1. 从所给线性表的第1个数据元素开始依次获取表中的数据元素
    2. 每获取一个数据元素,就为该数据元素生成一个新节点,将新节点插入到链表的尾部
    3. 插入完毕之后返回第 1 个链节点的地址
  3. 时间复杂度为O(n)
  4. 代码实现
# 根据一个data初始化一个新链表
def create(self, data):self.head() = ListNode(0)# 此时指针指向链表的开头cur = self.headfor i in range(len(data)):node = ListNode(data[i])# 每添加一个数据cur就往后移动一下cur.next = nodecur = cur.next

1.3.3 求链表长度

  1. 定义:链表的长度被定义为链表中包含的链节点的个数
  2. 步骤:使用一个可以顺着链表指针移动的指针变量 cur 和一个计数器 count
    1. 让指针变量 cur 指向链表的第 1 个链节点
    2. 然后顺着链节点的 next 指针遍历链表,指针变量 cur 每指向一个链节点,计数器就做一次计数。
    3. 等 cur 指向为空时结束遍历,此时计数器的数值就是链表的长度
  3. 时间复杂度为O(n)
  4. 代码实现
# 获取链表长度
def length(self):count = 0cur = self.headwhile cur:count += 1cur = cur.nextreturn count

1.3.4 查找元素

  1. 定义:在链表中查找值为 val 的位置
  2. 步骤:由于链表的特性只能从头结点开始访问,所以沿着链表一个节点一个节点的查找。如果查找成功,返回被查找结点的地址,否则返回None
  3. 时间复杂度为O(n)
  4. 代码实现
# 查找元素
def find(self, val)cur = self.headwhile cur:if cur.val == val:return curcur = cur.nextreturn None

1.3.5 插入元素

按照插入位置的不同,将插入操作分为三种

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

    1. 实现步骤:

      1. 先创建一个值为 val 的链节点 node
      2. 然后将 node 的 next 指针指向链表的头节点 head
      3. 再将链表的头节点 head 指向 node
    2. 时间复杂度:O(1)
    3. 代码实现:
    # 头部插入元素
    def insertFront(self, val):node = ListNode(val)node.next = self.headself.head = node
    
  2. 链表尾部插入元素:在链表最后 1 个链节点之后插入值为 val 的链节点

    1. 在链表的第index个位置插入元素

    2. 步骤:

      1. 使用指针变量 cur 和一个计数器 count。令 cur 指向链表的头节点,count 初始值赋值为 0
      2. 沿着链节点的 next 指针遍历链表,指针变量 cur 每指向一个链节点,计数器就做一次计数
      3. 当 count == index - 1 时,说明遍历到了第 index - 1 个链节点,此时停止遍历
      4. 创建一个值为 val 的链节点 node
      5. 将 node.next 指向 cur.next
      6. 然后令 cur.next 指向 node
    3. 时间复杂度:O(n)

    4. 代码实现:

    # 在链表的index位置插入数据
    def insertInside(self, index, val):count = 0cur = self.headwhile cur and count < index - 1:count += 1cur = cur.nextif not cur:return 'Error'node = ListNode(val)node.next = cur.nextcur.next = node
    
  3. 链表中间插入元素:在链表第 i 个链节点之前插入值为 val 的链节点

    1. 步骤:

      1. 先创建一个值为 val 的链节点 node
      2. 使用指针 cur 指向链表的头节点 head
      3. 通过链节点的 next 指针移动 cur 指针,从而遍历链表,直到 cur.next == None
      4. 令 cur.next 指向将新的链节点 node
    2. 时间复杂度:O(n)
    3. 代码实现
    # 在链表的末尾插入元素
    def insertRear(self, val):node = ListNode(val)cur = self.headwhile cur.next:cur = cur.nextcur.next = node
    

1.3.6 改变元素

  1. 定义:将链表中第 i 个元素值改为 val
  2. 步骤:先遍历到第 i 个链节点,然后直接更改第 i 个链节点的元素值
    1. 使用指针变量 cur 和一个计数器 count。令 cur 指向链表的头节点,count 初始值赋值为 0。
    2. 沿着链节点的 next 指针遍历链表,指针变量 cur 每指向一个链节点,计数器就做一次计数。
    3. 当 count == index 时,说明遍历到了第 index 个链节点,此时停止遍历
    4. 直接更改 cur 的值 val。
  3. 时间复杂度:O(n)
  4. 代码实现:
def change(self, index, val):count = 0cur = self.headwhile cur and count < index:count += 1cur = cur.nextif not cur:return 'Error'cur.val = val

1.3.7 删除元素

与插入元素相同,根据删除元素的所在位置将删除元素分为三种情况

  1. 链表头部删除元素

    1. 步骤:直接将 self.head 沿着 next 指针向右移动一步即可

    2. 时间复杂度:O(1)

    3. 代码实现:

    # 删除链表头部元素
    def removeFront(self):if self.head:self.head = self.head.next
    
  2. 链表中间删除元素

    1. 删除链表中第 i 个元素
    2. 步骤:
      1. 先使用指针变量 cur 移动到第 i - 1 个位置的链节点
      2. 然后将 cur 的 next 指针,指向要第 i 个元素的下一个节点即可
    3. 时间复杂度:O(n)
    4. 代码实现:
    # 删除链表第index个元素
    def removeInside(self, index):count = 0cur = self.headwhile cur.next and count < index - 1:count += 1cur = cur.nextif not cur:return 'Error'del_node = cur.nextcur.next = del_node.next
    
  3. 链表尾部删除元素

    1. 步骤:

      1. 先使用指针变量 cur 沿着 next 指针移动到倒数第 2 个链节点
      2. 然后将此节点的 next 指针指向 None 即可
    2. 时间复杂度:O(n)
    3. 代码实现:
    # 删除链表尾部元素
    def removeRear(self):if not self.head.next:return 'Error'cur = self.headwhile cur.next.next:cur = cur.nextcur.next = None
    

2. 链表相关题目(更新中)

206.反转链表.

  1. 思路讲解:由于我们需要将链表反转,也就是要将一个链节点的后继节点变为它的前驱节点,所以我们考虑使用双指针分别指向当前节点和后继节点
    但我们会发现,如果只有这两个指针,我们会造成如下的情况
    所以我们想到可以使用一个临时指针来指向节点的后继节点,即
    之后,将p的后继节点指向q,将p变为temp,将q变为p即可,依次迭代,即可得到答案。

    图片参考自此处.
  2. 时间复杂度:O(n)
  3. 代码实现:
    在代码中我使用prev,cur,next对应上图中的q,p, temp
# 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: ListNode) -> ListNode:prev, cur = None, headwhile cur is not None:next = cur.nextcur.next = prevprev = curcur = nextreturn prev

203.移除链表元素

  1. 思路:在这里,我们使用两个指针,第一个指针(cur)即指向当前位置,第二个指针(cur_next)即指向当前位置后第一个不等于val的位置,之后直接将第一个指针的next之后第二个指针即可
    注意,这个思路要涉及到特殊情况的处理(如[1, 1, 1, 2], [1, 1, 1]这类情况)
  2. 时间复杂度:O(n)
  3. 代码实现:
class Solution:def removeElements(self, head: ListNode, val: int) -> ListNode:if head == None:return headwhile head != None and head.val == val:head = head.nextif head == None:return headcur, cur_next = head, head.nextwhile cur_next != None:if cur_next.val == val:while cur_next != None and cur_next.val == val:cur_next = cur_next.nextcur.next = cur_nextelse:cur = cur_nextcur_next = cur_next.nextreturn head

328.奇偶链表

  1. 思路:整体上即为奇偶分离的思路

    1. 遍历:刚开始的思路,直接遍历,按照节点所在的位置的奇偶性将其划分为奇链表和偶链表,之后将奇链表的next指向偶链表即可。但这种方式的空间复杂度为O(n),不符合题目原地算法的要求
    2. 双指针:原始链表的头节点即为奇数链表的头节点,原始链表头结点的下一个节点即为偶数链表的头节点。用两个指针odd(奇数)和even(偶数)分别维护奇数节点和偶数节点。通过迭代的方式更新(注意更新的顺序)
  2. 时间复杂度:O(n)
  3. 代码实现:
class Solution:def oddEvenList(self, head: ListNode) -> ListNode:if not head:return headevenHead = head.nextodd, even = head, evenHeadwhile even and even.next:odd.next = even.nextodd = odd.nexteven.next = odd.nexteven = even.nextodd.next = evenHeadreturn head

234.回文链表

  1. 思路

    1. 数组:将链表中节点的数值存放在数组中,按照数组判断回文即可
    2. 先将后半部分链表逆序,之后判断前半部分和后半部分链表是否相等即可
      步骤:

      1. 利用快慢指针或者计算节点数量找到分界线
      2. 利用上面提到的反转链表的方法翻转链表
      3. 判断前半部分链表和后半部分链表是否相等
      4. 将被翻转的部分链表翻转回原状
  2. 相关复杂度:

    1. 数组:时间复杂度和空间复杂度都为O(n)
    2. 后一种:时间复杂度O(n),空间复杂度O(1)
  3. 代码实现:

class Solution:def isPalindrome(self, head: ListNode) -> bool:if head is None:return True# 找到前半部分链表的尾节点并反转后半部分链表first_half_end = self.end_of_first_half(head)second_half_start = self.reverse_list(first_half_end.next)# 判断是否回文result = Truefirst_position = headsecond_position = second_half_startwhile result and second_position is not None:if first_position.val != second_position.val:result = Falsefirst_position = first_position.nextsecond_position = second_position.next# 还原链表并返回结果first_half_end.next = self.reverse_list(second_half_start)return result    def end_of_first_half(self, head):fast = headslow = headwhile fast.next is not None and fast.next.next is not None:fast = fast.next.nextslow = slow.nextreturn slowdef reverse_list(self, head):previous = Nonecurrent = headwhile current is not None:next_node = current.nextcurrent.next = previousprevious = currentcurrent = next_nodereturn previous

python链表及其相关题目(更新中)相关推荐

  1. Python练习题__目录(更新中。。。)

    Python练习题__目录 人生有时真的很无助,特别是当社会走向了极端,当我们一无所知地憧憬着美好的未来时,却不知原本属于我们生来就拥有的一切,会被某些...暗中窥视后强行夺走(亲人.健康.技术.生命 ...

  2. Python日常小技巧(持续更新中)

    目录 快速定位元组内容 对字典进行排序 json的获取(dumps,dump,loads,load) 查找字典中相同的key 统计列表中元素的个数 字典按输入顺序输出 历史记录的存储 对有多个分割符的 ...

  3. 超好用Python小功能(持续更新中)

    文章目录 一.字符串操作小功能 1.把数字转为千位数值类型 2.检测字符串是不是纯数字 3.python列表的交.并.差集 4.对列表中字典中的字典排序 5.python 求角度大小 6.已知一个点, ...

  4. python 试题归纳及答疑 更新中.....

    一.Python基础篇(80题) 1.你为什么学习Python? 一.答题思路 1.阐述 python 优缺点 2.Python应用领域说明 3.根据自身工作情况阐述为什么会使用python 1)py ...

  5. python for bioinformatics相关题目

    题目完整版来自:http://rosalind.info/problems/list-view/: 学习的网友脚本来自生信技能树:http://www.biotrainee.com/forum-59- ...

  6. 2022PTA天梯赛-全国总决赛试题(个人python解题记录)(更新中)

    L1-1 今天我要赢 2018 年我们曾经出过一题,是输出"2018 我们要赢".今年是 2022 年,你要输出的句子变成了"我要赢!就在今天!"然后以比赛当天 ...

  7. python手记(游戏) 笨方法学python习题36【持续更新中】

    如有意见或其他问题可在下方写下评论或加QQ:1693121186 欢迎一起讨论技术问题! 代码如下: 解说:这是笨方法的习题36,让做一个游戏.我会持续更新,如果想复制玩玩的同学,请别将主线线人以下的 ...

  8. 豆瓣机器人小组自动回复回帖 Python 源码(持续更新中)

    最近给朋友做了一个豆瓣小组自动评论机器人,使用 requests 与 lxml 库,在控制刷新频率的情况下,基本能做到头排评论.除了爬虫的这一部分,还很重要的是要能对帖子回复有趣的内容. 基本功能 同 ...

  9. 【Python学习】常用函数(更新中……)

    系列文章目录 目录 系列文章目录 普通函数 一.输入输出函数 1. print()函数 2. input()函数 二.进制转换函数 1. bin(),oct(),hex()进制转换函数(带前缀) 2. ...

最新文章

  1. printf()输出
  2. 设置程序默认打开方式
  3. CommonJS概述及使用
  4. python 苹果id申请_如何申请百度机器翻译API的ID和Key,为Python调用做准备
  5. python爬虫验证码的识别_Python爬虫识别验证码
  6. hbase源码系列(八)从Snapshot恢复表
  7. 根据旋转角计算欧拉角 (Computing Euler angles from a rotation matrix)
  8. Codeforces 741D dsu on tree
  9. 【美化桌面】删除电脑桌面快捷键箭头
  10. 算法设计与分析 SCAU11083 旅游背包(优先做)
  11. cortana连不上网络_Alexa,为什么Cortana仍在我的计算机上?
  12. 掘金chrome插件安装失败怎么办?
  13. 联想国产自主计算机,实现零的突破,第一款纯国产电脑诞生,网友:此刻联想怎么想?...
  14. Excel如何提取身份证中出生年月日、计算年龄、性别
  15. 上确界和下确界的存在
  16. 深度解析Linux通过日志反查***
  17. 74160ENT引脚设计法+同步置数法接成365进制加法计数电路
  18. 几张产生视觉错觉的图片
  19. CS61A Proj 2
  20. macOS下修改iTunes的备份路径至移动硬盘提示Operation not permitted

热门文章

  1. 身体锻炼,饮食健康,生活规律,心情愉悦
  2. 关于intel 网卡 i217 驱动安装不上的问题综述
  3. ubuntu安装pylint
  4. php 中 Traits 详解
  5. (读书笔记)代码整洁之道-命名部分
  6. 基于java学生考勤管理系统设计——计算机毕业设计
  7. 特征工程到底是什么?
  8. html5 获取file文件绝对路径,H5中绝对路径和相对路径
  9. iOS开发之极光推送JPush
  10. android视频剪辑处理第三方,Android 中通过 FFmpeg 命令对音视频编辑处理