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

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 = val
		self.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.head
	for i in range(len(data)):
		node = ListNode(data[i])
		# 每添加一个数据cur就往后移动一下
		cur.next = node
		cur = 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 = 0
	cur = self.head
	while cur:
		count += 1
		cur = cur.next
	return count

1.3.4 查找元素

  1. 定义:在链表中查找值为 val 的位置
  2. 步骤:由于链表的特性只能从头结点开始访问,所以沿着链表一个节点一个节点的查找。如果查找成功,返回被查找结点的地址,否则返回None
  3. 时间复杂度为O(n)
  4. 代码实现
# 查找元素
def find(self, val)
	cur = self.head
	while cur:
		if cur.val == val:
			return cur
		cur = cur.next
	return 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.head
    	self.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 = 0
    	cur = self.head
    	while cur and count < index - 1:
    		count += 1
    		cur = cur.next
    	if not cur:
    		return 'Error'
    	node = ListNode(val)
    	node.next = cur.next
    	cur.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.head
    	while cur.next:
    		cur = cur.next
    	cur.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 = 0
	cur = self.head
	while cur and count < index:
		count += 1
		cur = cur.next
		
	if 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 = 0
    	cur = self.head
    	while cur.next and count < index - 1:
    		count += 1
    		cur = cur.next
    	if not cur:
    		return 'Error'
    	del_node = cur.next
    	cur.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.head
    	while cur.next.next:
    		cur = cur.next
    	cur.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, head
        while cur is not None:
            next = cur.next
            cur.next = prev
            prev = cur
            cur = next
        return 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 head
        while head != None and head.val == val:
            head = head.next
        if head == None:
            return head
        
        cur, cur_next = head, head.next
        while cur_next != None:
            if cur_next.val == val:
                while cur_next != None and cur_next.val == val:
                    cur_next = cur_next.next
                cur.next = cur_next
            else:
                cur = cur_next
                cur_next = cur_next.next
        return 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 head
        evenHead = head.next
        odd, even = head, evenHead
        while even and even.next:
            odd.next = even.next
            odd = odd.next
            even.next = odd.next
            even = even.next
        odd.next = evenHead
        return 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 = True
        first_position = head
        second_position = second_half_start
        while result and second_position is not None:
            if first_position.val != second_position.val:
                result = False
            first_position = first_position.next
            second_position = second_position.next

        # 还原链表并返回结果
        first_half_end.next = self.reverse_list(second_half_start)
        return result    

    def end_of_first_half(self, head):
        fast = head
        slow = head
        while fast.next is not None and fast.next.next is not None:
            fast = fast.next.next
            slow = slow.next
        return slow

    def reverse_list(self, head):
        previous = None
        current = head
        while current is not None:
            next_node = current.next
            current.next = previous
            previous = current
            current = next_node
        return previous
  • 3
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值