LeetCode刷题必知的数据结构

常考:Array,String,LinkedList,Tree(BT,BST),Stack,Queue,PriorityQueue(Heap),HashMap,HashSet

少考:Trie,Disjoint-Set(Union Find),Deque,Gragh

一般不考,但可以用来一题多解更快:TreeMap,TreeSet,Segment Tree(zkw Tree),Binary Index Tree(Fenwick Tree)

基本数据结构了解一般需要配合算法使用,比如gragh图基本的考点在于其中的搜索算法,又或者很多动态规划就是基于最基础的string,array这种数据结构

1.数组

定义:在连续的内存空间中,存储一组相同类型的元素。可以在内存中连续存储多个元素的结构,在内存中的分配也是连续的,数组中的元素通过数组下标进行访问,数组下标从0开始

访问Access:O(1)

搜索Search:O(N)

插入Insert:O(N)

删除Delete:O(N)

特点:适合读,不适合写

优点:按照索引查询元素速度快

           按照索引遍历数组方便

缺点:数组的大小固定后就无法扩容了

           数组只能存储一种类型的数据

           添加,删除的操作慢,因为要移动其它的元素

适用场景:频繁查询,对存储空间要求不大,很少增加和删除的情况

2.链表

LeetCode刷题一般只需要单端链表

链表是物理存储单元上非连续的,非顺序的存储结构,数据元素的逻辑顺序是通过链表的指针地址实现,每个元素包含两个结点,一个是存储元素的数据域(内存空间),另一个是指向下一个结点地址的指针域。根据指针的指向,链表能形成不同的结构,例如单链表,双向链表,循环链表等

常用的method,赋值取值时间复杂度均为O(1)

初始化 ListNode head = new ListNode(0);

赋值 head.next = new ListNode(1);

取值 head.val

访问Access:O(N)

搜索Search:O(N)

插入Insert:O(1)

删除Delete:O(1)

特点:写很快,读很慢

优点:是很常用的一种数据结构,不需要初始化容量,可以任意加减元素;添加或者删除元素时只需要改变前后两个元素结点的指针域指向地址即可,所以添加,删除很快

缺点:因为含有大量的指针域,占用空间较大,查找元素需要遍历链表来查找,非常耗时

适用场景:读少写多,数据量较小,需要频繁增加,删除操作的场景

结构:LeetCode都给出来了

3.队列

队列是一种线性表,可以在一端添加元素,在另一端取出元素,也就是先进先出,从一端放入元素的操作称为入队,取出元素为出队

常用来实现 BFS 宽度优先搜索的遍历

单端队列:只有一个口可以进,一个口可以出

双端队列:两个口都可以进,两个口都可以出

访问Access:O(N)

搜索Search:O(N)

插入Insert:O(1)

删除Delete:O(1)

Deque:双端队列(Double - ended queue)

1. 允许两头都进,两头都出

2. Java集合提供了接口Deque来实现一个双端队列,它的功能是既可以添加到队尾,也可以添加到队首;既可以从队首获取,又可以从队尾获取

3. 初始化:Deque deque = new ArrayDeque<>():

4.栈

栈是一种特殊的线性表,仅能在线性表的一端操作,栈顶允许操作,栈底不允许操作

甲骨文官方doc推荐使用Deque来代替Stack,因为内部实现更合理

特点:先进后出,或者说后进先出,从栈顶放入元素的操作较入栈,取出元素叫出栈

栈的结构就像一个集装箱,越先放进去的东西越晚才能拿出来,所以,栈常应用于实现递归功能方面的场景,DFS均可以使用栈来实现

初始化:Stack stack = new Stack<>();

常用method(时间复杂度均为O(1)):stack.push(num),stack.peek(), 

                                                              stack.pop(),stack.isEmpty()

访问Access:O(1) 栈顶元素

搜索Search:O(N)

插入Insert:O(1)

删除Delete:O(1) 栈顶元素

5.哈希表

也叫散列表(Java HashMap),是根据关键码和值(key和value)直接进行访问的数据结构,通过key和value来映射到集合中的一个位置,这样就可以很快找到集合中的对应元素

如果两个不同的string input作为key在hashmap中index出现了冲突如何处理?一般使用两种方法:①挂链表 ②开放地址法

好处:查找比纯链表快,插入删除比纯数组快

key:value    键值对

Java:HashMap

Python:字典

key → 哈希函数 → 内存地址 → key/value放到对应的内存地址中

用数组创建哈希表 → 明确数组里有多少元素

用自带函数创建哈希表 → HashMap,dict

哈希碰撞:两个不同的key通过同一个哈希函数得到相同的内存地址

访问Access:×

搜索Search:O(1) 

插入Insert:O(1)

删除Delete:O(1)

碰撞:O(K)(K为碰撞元素的个数)

6.集合

特点:无序,不重复,允许包含值为null的元素,但最多只能有一个null元素

主要作用:检查某一个元素是否存在;查看是否有重复元素

用得最多的是HashSet

它是基于HashMap实现的,HashSet底层使用HashMap来保存所有元素,因此HashSet的实现比较简单,有关HashSet的操作,基本上都是直接调用底层HashMap的相关方法来完成

初始化:Set set = new HashSet<>();

访问Access:×

搜索Search:O(1) 无哈希冲突

插入Insert:O(1) 无哈希冲突

删除Delete:O(1) 无哈希冲突

Disjoint - Set(Union Find):并查集是一种树形的数据结构,用于处理不交集的合并(union)及查询(find)问题。找几个数字是否在同一个group里面

7.树

树在工作中一般自己单独使用的机会很少

在刷题过程中,LeetCode会直接给你树的结构

树是由n(n >= 1)个有限结点组成一个具有层次关系的集合

Binary Tree二叉树是树的特殊一种,具有的特点是:①每个结点最多有两颗子树 ②左子树和右子树是有顺序的

初始化:TreeNode root = new TreeNode(0);

               root.left = new TreeNode(1);

               root.right = new TreeNode(2);

普通二叉树:每个节点最多两个孩子

满二叉树:除了叶子节点,每个节点都有左右两个孩子,所有叶子节点在同一层上

完全二叉树:从树的根节点,从上到下,从左到右依次填满节点形成的二叉树

前序遍历:根节点 → 左子树 → 右子树

中序遍历:左子树 → 根节点 → 右子树

后序遍历:左子树 → 右子树 → 根节点

TreeMap:和hashmap几乎一样的用法,但是提供了key本身有顺序了。面试不做要求,因为这只是Java独有的class,不使用treemap也可以解题,只是用treemap代码可以更短一些方便

Trie:前缀树或字典树

性质:根节点不包含字符,除根节点外的每一个节点都仅包含一个字符;从根节点到某一节点路径上所经过的字符连接起来,即为该节点对应的字符串;任意节点的所有子节点所包含的字符都不相同;就像一棵 N-array 树

基础method:addWord(),searchWord(),searchPrefix(),时间复杂度均为word.length

Segment Tree 线段树

实现方式有:tree指针实现的线段树,zkw线段树

用来解决什么问题?

给定一个长度为 n 的序列,需要频繁求其中某个区间的最值,以及更新某个区间的所有值

可以logn求区间最大,最小,rangeSum等

常用method

1.单点修改(logn)update(index,value)

2.区间修改(这里需要用到lazy propogation来优化到logn,完全不在面试范围)

3.区间查询(logn区间求和,求区间最大值,求区间最小值,区间最小公倍数,区间最大公因数)

面试极少考,入门不做要求

一般求rangeSum可以考虑用Binary Index Tree或者preFix sum解决

8.堆

定义:是一种二叉树的结构

考察点一般为pq应用和array手动实现heap

堆分为两种:最大堆和最小堆,两者的差别在于节点的排序方式

在最大堆中,父节点的值比每一个子节点的值都要大。在最小堆中,父节点的值比每一个子节点的值都要小。这就是所谓的 “堆属性”,并且这个属性对堆中的每一个节点都成立

初始化:PriorityQueue pq = new PriorityQueue<>();

Java PriorityQueue默认最小堆,初始化一个一个加是O(logn),一次很多数是O(n)

条件:①完全二叉树②每个节点≥孩子节点(最大堆)或每个节点 ≤孩子节点(最小堆)

访问Access:×

搜索Search:O(1)  堆顶元素

插入Insert:O(logN)

删除Delete:O(logN)  堆顶元素

9.字符串

严格说不是数据结构,是一种non-primitive data type

primitive type指的是boolean,int,char,double,long,byte,short,float

non-primitive type指的是Strings,Arrays,Classes,Interfaces

常用的method:str.substring();str.charAt(index);str1.compareTo(str2);

10.图

存储具有 “多对多” 逻辑关系的数据结构

图的表示有两种主要方式:邻接表和邻接矩阵

图类型考察不多,一般基本BFS,DFS,拓扑排序即可

深入学习可以考虑最短路径,最小生成树等

11.Comparable 与 Comparator

java中,对集合对象或者数组对象排序,有两种实现方式

即:(1)对象实现comparable接口

       (2)定义比较器,实现comparator接口(内部可以用传统写法或者lambda写法)

两者的区别是一个是集合内部实现,一个是集合外部实现

class Person implements Comparable{
  @override
  public int compareTo(Person person) {
    return name.compareTo(person.name);
  }
}
Collections.sort(people,new Comparator() {
  @override
  public int compare(Person a,Person b) {
    return a.age 

Comparable是一个对象,本身就已经支持自比较所需要实现的接口

自定义类要在加入list容器中后能够排序,也可以实现Comparable接口

在用Collections类的sort方法排序时若不指定Comparator,那就以自然顺序排序,所谓自然顺序就是实现Comparable接口设定的排序方式

若一个类实现了comparable接口,则意味着该类支持排序。如String,Integer自己就实现了Comparable接口,可完成比较大小操作

一个已经实现comparable的类的对象或数据,可以通过Collections.sort(list)或者Arrays.sort(arr)实现排序。通过Collections.sort(list,Collections.reverseOrder());对list进行倒序排列

Comparator是在集合外部实现的排序,位于java.util下。Comparator接口包含了两个函数

我们若需要控制某个类的次序,而该类本身不支持排序(即没有实现Comparable接口);那么,我们可以新建一个该类的比较器来进行排序。这个比较器只需要实现comparator即可

如果引用的为第三方jar包,这时候,没办法改变类本身,可是使用这种方式

Comparator是一个专用的比较器,当这个对象不支持自比较或者自比较函数不能满足要求时,可写一个比较器来完成两个对象之间大小的比较

Comparator体现了一种策略模式,就是不改变对象自身,而用一个策略对象来改变它的行为

Comparable相当于内部比较器,Comparator相当于外部比较器

总结

1.一般觉得lambda写起来很方便,面试也可以说如果越界用Integer.compare

2.万一被问到,也可以外置comparator,或者object内置comparable去实现

3.hashCode() and equals() 都用同一个field去生成

4.hashCode() 和equals() 要同时去override,二者的结果应该一个相同,另一个也必须相同

5.Comparator还常用于priorityQueue

你可能感兴趣的:(数据结构与算法,数据结构)