失效链接处理 |
labuladong的算法秘籍V1.7(力扣版) PDF 下载
本站整理下载:
相关截图:
![]()
主要内容:
⼀、数据结构的存储⽅式
数据结构的存储⽅式只有两种:数组(顺序存储)和链表(链式存储)。
这句话怎么理解,不是还有散列表、栈、队列、堆、树、图等等各种数据结构吗?
我们分析问题,⼀定要有递归的思想,⾃顶向下,从抽象到具体。你上来就列出这么多,那些都属于「上层
建筑」,⽽数组和链表才是「结构基础」。因为那些多样化的数据结构,究其源头,都是在链表或者数组上
的特殊操作,API 不同⽽已。
⽐如说「队列」、「栈」这两种数据结构既可以使⽤链表也可以使⽤数组实现。⽤数组实现,就要处理扩容
缩容的问题;⽤链表实现,没有这个问题,但需要更多的内存空间存储节点指针。
「图」的两种表示⽅法,邻接表就是链表,邻接矩阵就是⼆维数组。邻接矩阵判断连通性迅速,并可以进⾏
矩阵运算解决⼀些问题,但是如果图⽐较稀疏的话很耗费空间。邻接表⽐较节省空间,但是很多操作的效率
上肯定⽐不过邻接矩阵。
「散列表」就是通过散列函数把键映射到⼀个⼤数组⾥。⽽且对于解决散列冲突的⽅法,拉链法需要链表特
性,操作简单,但需要额外的空间存储指针;线性探查法就需要数组特性,以便连续寻址,不需要指针的存
储空间,但操作稍微复杂些。
「树」,⽤数组实现就是「堆」,因为「堆」是⼀个完全⼆叉树,⽤数组存储不需要节点指针,操作也⽐较
简单;⽤链表实现就是很常⻅的那种「树」,因为不⼀定是完全⼆叉树,所以不适合⽤数组存储。为此,在
这种链表「树」结构之上,⼜衍⽣出各种巧妙的设计,⽐如⼆叉搜索树、AVL 树、红⿊树、区间树、B 树等
等,以应对不同的问题。
在线⽹站 labuladong的刷题三件套
12 / 692
了解 Redis 数据库的朋友可能也知道,Redis 提供列表、字符串、集合等等⼏种常⽤数据结构,但是对于每
种数据结构,底层的存储⽅式都⾄少有两种,以便于根据存储数据的实际情况使⽤合适的存储⽅式。
综上,数据结构种类很多,甚⾄你也可以发明⾃⼰的数据结构,但是底层存储⽆⾮数组或者链表,⼆者的优
缺点如下:
数组由于是紧凑连续存储,可以随机访问,通过索引快速找到对应元素,⽽且相对节约存储空间。但正因为连
续存储,内存空间必须⼀次性分配够,所以说数组如果要扩容,需要重新分配⼀块更⼤的空间,再把数据全
部复制过去,时间复杂度 O(N);⽽且你如果想在数组中间进⾏插⼊和删除,每次必须搬移后⾯的所有数据以
保持连续,时间复杂度 O(N)。
链表因为元素不连续,⽽是靠指针指向下⼀个元素的位置,所以不存在数组的扩容问题;如果知道某⼀元素
的前驱和后驱,操作指针即可删除该元素或者插⼊新元素,时间复杂度 O(1)。但是正因为存储空间不连续,
你⽆法根据⼀个索引算出对应元素的地址,所以不能随机访问;⽽且由于每个元素必须存储指向前后元素位
置的指针,会消耗相对更多的储存空间。
⼆、数据结构的基本操作
对于任何数据结构,其基本操作⽆⾮遍历 + 访问,再具体⼀点就是:增删查改。
数据结构种类很多,但它们存在的⽬的都是在不同的应⽤场景,尽可能⾼效地增删查改。话说这不就是数据
结构的使命么?
如何遍历 + 访问?我们仍然从最⾼层来看,各种数据结构的遍历 + 访问⽆⾮两种形式:线性的和⾮线性的。
线性就是 for/while 迭代为代表,⾮线性就是递归为代表。再具体⼀步,⽆⾮以下⼏种框架:
数组遍历框架,典型的线性迭代结构:
void traverse(int[] arr) {
for (int i = 0; i < arr.length; i++) {
// 迭代访问 arr[i]
}
}
链表遍历框架,兼具迭代和递归结构:
/* 基本的单链表节点 */
class ListNode {
int val;
ListNode next;
}
void traverse(ListNode head) {
for (ListNode p = head; p != null; p = p.next) {
// 迭代访问 p.val
}
}
void traverse(ListNode head) {
在线⽹站 labuladong的刷题三件套
13 / 692
// 递归访问 head.val
traverse(head.next);
}
⼆叉树遍历框架,典型的⾮线性递归遍历结构:
/* 基本的⼆叉树节点 */
class TreeNode {
int val;
TreeNode left, right;
}
void traverse(TreeNode root) {
traverse(root.left);
traverse(root.right);
}
你看⼆叉树的递归遍历⽅式和链表的递归遍历⽅式,相似不?再看看⼆叉树结构和单链表结构,相似不?如
果再多⼏条叉,N 叉树你会不会遍历?
⼆叉树框架可以扩展为 N 叉树的遍历框架:
/* 基本的 N 叉树节点 */
class TreeNode {
int val;
TreeNode[] children;
}
void traverse(TreeNode root) {
for (TreeNode child : root.children)
traverse(child);
}
N 叉树的遍历⼜可以扩展为图的遍历,因为图就是好⼏ N 叉棵树的结合体。你说图是可能出现环的?这个很
好办,⽤个布尔数组 visited 做标记就⾏了,这⾥就不写代码了。
所谓框架,就是套路。不管增删查改,这些代码都是永远⽆法脱离的结构,你可以把这个结构作为⼤纲,根
据具体问题在框架上添加代码就⾏了,下⾯会具体举例。
三、算法刷题指南
⾸先要明确的是,数据结构是⼯具,算法是通过合适的⼯具解决特定问题的⽅法。也就是说,学习算法之
前,最起码得了解那些常⽤的数据结构,了解它们的特性和缺陷。
那么该如何在 LeetCode 刷题呢?之前的⽂章写过⼀些,什么按标签刷,坚持下去云云。现在距那篇⽂章已
经过去将近⼀年了,我不说那些不痛不痒的话,直接说具体的建议:
先刷⼆叉树,先刷⼆叉树,先刷⼆叉树!
|