前言
一文带你了解链表的基础,并且其经常考察的思维算法。本文用于记录自己的学习过程,同时向大家进行分享相关的内容。本文内容参考于 代码随想录 同时包含了自己的许多学习思考过程,如果有错误的地方欢迎批评指正!
链表理论基础
什么是链表?链表就是通过一系列的指针串联在一起的线性结构。每个节点由数据域和指针域组成,每个指针域存储着指向下一个节点的地址。最后一个节点的指针域指向NULL。一般来说,链表都会有一个头节点,其数据域不存储数据,指针域指向第一个有效节点。为什么存在头节点呢?头节点的存在是为了便于链表的增删改查操作的,具体如何实现,后续会有体现。
链表的形式有很多,刚才所讲述的是单链表,其只能向后检索,因为其只有指向下个节点的指针。还有双向链表,即有两个指针域,不仅存在着指向下个节点的指针域,还存在着指向上个节点的指针域,即双向链表既可以向后查询,也可以向前查询。
当然还有循环链表,即最后一个节点的指针域指向头节点,即为循环链表。其是用来解决约瑟夫问题的。
链表的存储并不像数组一样,存储地址连续分布,因其指针域指向下个节点,所以链表的存储是任意的,只需要让其指针域指向下个节点即可形成链表。因其独特的定义形式所以其最基础从增删改查操作与数组是不同的。
- 删除操作:我们只需要将指针域从指向删除节点改为指向删除节点的下个节点即可
- 增加节点:我们将增加的节点的指针域指向添加位置节点所指向的指针域,然后在将添加位置节点所指向的指针域改为指向增加的节点
链表实战算法
设计链表
当你能够设计出相关实现功能的列表之后,你对于列表知识的掌握就已经有十之七八了。
[设计链表](707. 设计链表 - 力扣(LeetCode))
相关技巧:我们设计链表实现相关的功能的时候,最基础的就是应该有一个头节点,有一个变量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))
相关技巧:其实这道题的思路还是比较简单的,我们可以结合数组学到的双指针法来做,初始状态,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))
相关技巧:首先看题目要求说不能交换值,那么意思就是只能来交换节点,所以考察的就是节点的指针域的改变,涉及到多个节点的指针域交换问题,我们最好是能够来画图表示,要不然很容易出现错误的。其次,我们可加入虚拟头节点,这样能够更加的便于我们的解题。其次来看,有两个节点交换,那么肯定其下下个节点均存在才可行。即cur.next和cur.next.next都存在才能进行节点的交换,这样理解的话,该题目就很简单了。
# 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))
相关技巧:我们需要删除倒数第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