链表基础理论
链表是通过指针串联在一起的线性结构,每个结点由两部分组成——数据域和指针域(存放指向下一节点的指针)最后一个节点的指针域指向Null(空指针)
- 单链表中的指针域只能指向节点的下一个节点。双链表的每个节点有两个指针域分别指向上一个节点和下一个节点。
- 即单链表只能向后查询,双链表可以向前向后查询。
- 循环链表首尾相连,可以解决约瑟夫环问题
链表在内存中并不是连续分布的,而是散乱分布在内存中的某地之上,分配机制取决于操作系统的内存管理。
链表的定义(含C++和Python两种
//单链表
struct ListNode{
int val; //节点上的存储元素
ListNode *next; //指向下一节点的指针
ListNode(int x) : val(x), next(NULL) {} //节点的构造函数
};
class ListNode:
def __init__(self,val = 0, next = None):
self.val = val
self.next = next
性能分析
-
数组在定义的时候,长度就是固定的,如果想改动数组的长度,就需要重新定义一个新的数组。
-
链表的长度可以是不固定的,并且可以动态增删, 适合数据量不固定,频繁增删,较少查询的场景。
203 移除链表元素
我的错误思路:这个思路主要错在没有考虑head节点对应的值也是目标值的情况
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
cur = head
while cur:
if cur.next.val == val:
cur.next = cur.next.next
cur = cur.next
return head
正确思路:首先我们需要设置一个永远指向head的指针,以应对当head节点的值也是目标值需要删掉的情况。
在链表中,当我们需要删除当前节点时需要使用它的上一节点的next
使其指向当前节点的下一节点。
因此我们设置cur
来遍历整个链表,设置pre
作为cur
的上一节点。
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
# 设置一个永远指向head的指针
headpre = ListNode()
headpre.next = head
# cur节点用于遍历链表
cur = head
# pre节点始终指向cur节点的前一个节点,方便删除节点
pre = headpre
while cur:
if cur.val == val:
pre.next = cur.next
cur = cur.next
else: #此处cur和pre顺序随意,都可以
cur = cur.next
pre = pre.next
return headpre.next
思想一致,但更简练一些,这种写法值得学习一下
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
#添加辅助的虚拟节点 令其指向头节点
prehead = ListNode(next = head)
cur = prehead
while cur.next:
if cur.next.val == val:
cur.next = cur.next.next
else:
cur = cur.next
return prehead.next
707 设计链表
这道题我踩了很多坑,首先我们在设计每个函数的时候一定要明确它的极端情况的边界条件,否则很容易报错。我们可以边写边画每个流程,就能够更直观清楚的防止这种错误的发生。
# 首先定义结点
class ListNode:
def __init__(self,val = 0, next = None):
self.val = val
self.next = next
class MyLinkedList:
def __init__(self):
# 通过创建虚拟头结点进行操作
self.head = ListNode()
# 记录当前结点数量
self.size = 0
def get(self, index: int) -> int:
# 判断极端条件
# 注意此处index=self.size时下标无效
if index < 0 or index >= self.size:
return -1
cur = self.head.next
for _ in range(index):
cur = cur.next
return cur.val
# addAtHead和addAtTail本质上都是addAtIndex的特殊情况,可以直接调用addAtIndex
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:
# 考虑极端情况
# 注意此处index = self.size时意味着将值为val的结点加到链表的尾部,因此此处条件需要是index>self.size
if index < 0 or index > self.size:
return
# 注意此处不要使用pre=self.head.next,否则循环最后pre将会有None的情况,最后pre.next不成立了
pre = self.head
for _ in range(index):
pre = pre.next
pre.next = ListNode(val,pre.next)
# 注意我们的方法addAtHead和addAtTail都是基于addAtIndex的特殊情况,因此不要在addAtHead和addAtTail中对self.size进行+1的操作
self.size+=1
def deleteAtIndex(self, index: int) -> None:
if index < 0 or index >=self.size:
return
pre = self.head
for _ in range(index):
pre = pre.next
t = pre.next
pre.next = t.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
反转链表
主要用到了双指针的思想,设置了两个指针cur和pre,cur遍历整个链表,并将其next指针转移,pre存放反转后的链表。
# 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:
# 定义一个cur指向头节点,遍历整个链表
cur = head
pre = None
while cur:
tmp = cur.next
cur.next = pre
# 更新pre和cur指针
pre = cur #这步很容易忽略!!!
cur = tmp
return pre
用到了递归的思想
# 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:
return self.reverse(head,None)
def reverse(self,cur:ListNode,pre:ListNode) -> ListNode:
if cur == None:
return pre
tmp = cur.next
cur.next = pre
return self.reverse(tmp,cur)