1. 数组
数组(Array)是一种线性表数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。
线性表就是数据排成像一条线一样的结构。每个线性表上的数据最多只有前和后两个方向。其实除了数组,链表、队列、栈等也是线性表结构。与它相对立的概念是非线性表,比如二叉树、堆、图等。之所以叫非线性,是因为,在非线性表中,数据之间并不是简单的前后关系。


低效的“插入”和“删除”;
数组支持随机访问,根据下标随机访问的时间复杂度为 O(1)。
2. 链表
2.1. 链表结构
数组需要一块连续的内存空间来存储,对内存的要求比较高。
而链表恰恰并不需要一块连续的内存空间,它通过“指针”将一组零散的内存块串联起来使用。

三种最常见的链表结构,分别是:单链表、双向链表和循环链表。




双向链表需要额外的两个空间来存储后继结点和前驱结点的地址。所以,如果存储同样多的数据,双向链表要比单链表占用更多的内存空间。
结构上来看,双向链表可以支持 O(1) 时间复杂度的情况下找到前驱结点,正是这样的特点,也使双向链表在某些情况下的插入、删除等操作都要比单链表简单、高效。
在实际的软件开发中,从链表中删除一个数据无外乎这两种情况:
- 删除结点中“值等于某个给定值”的结点;
- 删除给定指针指向的结点。
对于第一种情况,不管是单链表还是双向链表,为了查找到值等于给定值的结点,都需要从头结点开始一个一个依次遍历对比,尽管单纯的删除操作时间复杂度是 O(1),但遍历查找的时间是主要的耗时点,对应的时间复杂度为 O(n)。
对于第二种情况,我们已经找到了要删除的结点,但是删除某个结点 q 需要知道其前驱结点,而单链表并不支持直接获取前驱结点,所以,为了找到前驱结点,我们还是要从头结点开始遍历链表,直到 p->next=q,说明 p 是 q 的前驱结点。但是对于双向链表来说,因为双向链表中的结点已经保存了前驱结点的指针,不需要像单链表那样遍历。所以,针对第二种情况,单链表删除操作需要 O(n) 的时间复杂度,而双向链表只需要在 O(1) 的时间复杂度内就搞定了。
对于执行较慢的程序,可以通过消耗更多的内存(空间换时间)来进行优化;而消耗过多内存的程序,可以通过消耗更多的时间(时间换空间)来降低内存的消耗。

2.2. 链表 VS 数组性能大比拼

数组和链表的对比,并不能局限于时间复杂度。而且,在实际的软件开发中,不能仅仅利用复杂度分析就决定使用哪个数据结构来存储数据。
2.3. 虚拟头结点

# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
# 创建虚拟头部节点以简化删除过程
dummy_head = ListNode(next = head)
# 遍历列表并删除值为val的节点
current = dummy_head
while current.next:
if current.next.val == val:
current.next = current.next.next
else:
current = current.next
return dummy_head.next
2.4. 重点留意边界条件处理
软件开发中,代码在一些边界或者异常情况下,最容易产生 Bug。链表代码也不例外。要实现没有 Bug 的链表代码,一定要在编写的过程中以及编写完成之后,检查边界条件是否考虑全面,以及代码在边界条件下是否能正确运行。
- 如果链表为空时,代码是否能正常工作?
- 如果链表只包含一个结点时,代码是否能正常工作?
- 如果链表只包含两个结点时,代码是否能正常工作?
- 代码逻辑在处理头结点和尾结点的时候,是否能正常工作?
2.5. 链表代码随想录
1. 206.反转链表
多指针: pre和currrent,还有临时指针temp

class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def reverseList(self, head):
current = head
pre = None
while current:
temp = current.next
current.next = pre
pre = current
current = temp
return pre
2. 707.设计链表:
注意虚拟头节点的用法
3. 206.反转链表:
多指针的用法,不要让链表后面的元素断掉即可,然后根据需要依次指向
4. 24.两两交换链表中的节点:
虚拟头节点,多指针的用法,依次交换一个节点后面的两个节点的顺序
5. 19.删除链表中的倒数第N个节点:
快慢指针只需要遍历一遍链表即可
6. 面试题02.07.链表相交:
求长度,末尾对其,指针同时出发,降低时间复杂度
7. 142.环形链表II:
快慢指针(较复杂),或者采用容器(集合)的方法,判断元素是否以及在容器中存在

2778

被折叠的 条评论
为什么被折叠?



