走进链表及其经典考察题目

前言

一文带你了解链表的基础,并且其经常考察的思维算法。本文用于记录自己的学习过程,同时向大家进行分享相关的内容。本文内容参考于 代码随想录 同时包含了自己的许多学习思考过程,如果有错误的地方欢迎批评指正!

链表理论基础

什么是链表?链表就是通过一系列的指针串联在一起的线性结构。每个节点由数据域和指针域组成,每个指针域存储着指向下一个节点的地址。最后一个节点的指针域指向NULL。一般来说,链表都会有一个头节点,其数据域不存储数据,指针域指向第一个有效节点。为什么存在头节点呢?头节点的存在是为了便于链表的增删改查操作的,具体如何实现,后续会有体现。

image-20250407193409116

链表的形式有很多,刚才所讲述的是单链表,其只能向后检索,因为其只有指向下个节点的指针。还有双向链表,即有两个指针域,不仅存在着指向下个节点的指针域,还存在着指向上个节点的指针域,即双向链表既可以向后查询,也可以向前查询。

image-20250407193636928

当然还有循环链表,即最后一个节点的指针域指向头节点,即为循环链表。其是用来解决约瑟夫问题的。

image-20250407193724401

链表的存储并不像数组一样,存储地址连续分布,因其指针域指向下个节点,所以链表的存储是任意的,只需要让其指针域指向下个节点即可形成链表。因其独特的定义形式所以其最基础从增删改查操作与数组是不同的。

  • 删除操作:我们只需要将指针域从指向删除节点改为指向删除节点的下个节点即可

image-20250407194218925

  • 增加节点:我们将增加的节点的指针域指向添加位置节点所指向的指针域,然后在将添加位置节点所指向的指针域改为指向增加的节点

image-20250407194518691

链表实战算法

设计链表

当你能够设计出相关实现功能的列表之后,你对于列表知识的掌握就已经有十之七八了。

[设计链表](707. 设计链表 - 力扣(LeetCode))

image-20250407195111616

相关技巧:我们设计链表实现相关的功能的时候,最基础的就是应该有一个头节点,有一个变量size用来表示链表的长度。当然有些特殊的问题可能需要更多的参数,但头节点和size应是必备的。该题还是比较简单的,所需要实现的功能也基本都是链表的基础功能,但是写代码的时候确实要注意很多细节部分,仅仅理论是不够的,我们更要学会如何去写。

代码如下:

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

    def get(self, index: int) -> int:
        if index < 0 or index >= self.size:
            return -1
        
        current = self.dummy_head.next
        for i in range(index):
            current = current.next
            
        return current.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 < 0 or index > self.size:
            return
        
        current = self.dummy_head
        for i 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
        
        current = self.dummy_head
        for i in range(index):
            current = current.next
        current.next = current.next.next
        self.size -= 1

反转链表

[反转链表](206. 反转链表 - 力扣(LeetCode))

image-20250408092016338

相关技巧:其实这道题的思路还是比较简单的,我们可以结合数组学到的双指针法来做,初始状态,pre为None,cur指向第一个节点,然后令cur每次指向pre,在两个同时向下移动一位,但这里需要注意下,我们改变cur指针指向后,cur就不再指向原本的下一位了,所以这里我们用个tmp来临时存储cur的下个节点,如此循环,即可完成反转链表功能。

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

两两交换链表节点

[两两交换链表节点](24. 两两交换链表中的节点 - 力扣(LeetCode))

image-20250408093431337

相关技巧:首先看题目要求说不能交换值,那么意思就是只能来交换节点,所以考察的就是节点的指针域的改变,涉及到多个节点的指针域交换问题,我们最好是能够来画图表示,要不然很容易出现错误的。其次,我们可加入虚拟头节点,这样能够更加的便于我们的解题。其次来看,有两个节点交换,那么肯定其下下个节点均存在才可行。即cur.nextcur.next.next都存在才能进行节点的交换,这样理解的话,该题目就很简单了。

image-20250408093901008

# 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: ListNode) -> ListNode:
        dummy_head = ListNode(next=head)
        current = dummy_head
        
        # 必须有cur的下一个和下下个才能交换,否则说明已经交换结束了
        while current.next and current.next.next:
            temp = current.next # 防止节点修改
            temp1 = current.next.next.next
            
            current.next = current.next.next
            current.next.next = temp
            temp.next = temp1
            current = current.next.next
        return dummy_head.next

删除链表的倒数第N个节点

[删除链表的倒数第N个节点](19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode))

image-20250408100307836

相关技巧:我们需要删除倒数第N个节点,但是我们也不知到其具体的size大小,那怎么办?这里我们同样可以用到双指针法,这里有个技巧,我们需要删除倒数第N个节点,那么最终的fast指针肯定会遍历到最后的,而slow指针最终与fast相差N,那我们就可以先让fast走N+1个节点,为什么N+1,因为删除节点时我们需要其前一个节点来操作,所以让fast多走N+1个节点。再让fast和slow一起走,直至fast为空,那么我们slow就到了需要删除的节点位置的前一个节点了。

# 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: ListNode, n: int) -> ListNode:
        # 创建一个虚拟节点,并将其下一个指针设置为链表的头部
        dummy_head = ListNode(0, head)
        
        # 创建两个指针,慢指针和快指针,并将它们初始化为虚拟节点
        slow = fast = dummy_head
        
        # 快指针比慢指针快 n+1 步
        for i in range(n+1):
            fast = fast.next
        
        # 移动两个指针,直到快速指针到达链表的末尾
        while fast:
            slow = slow.next
            fast = fast.next
        
        # 通过更新第 (n-1) 个节点的 next 指针删除第 n 个节点
        slow.next = slow.next.next
        
        return dummy_head.next
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值