diff --git a/1 String/KMP.md b/1 String/KMP.md new file mode 100644 index 0000000..03d0bd1 --- /dev/null +++ b/1 String/KMP.md @@ -0,0 +1,7 @@ +# 字符串匹配算法 KMP + +KMP于1977年被提出,全称 Knuth–Morris–Pratt 算法; 名字分别是:Donald Knuth(K), James H. Morris(M), Vaughan Pratt(P). + +KMP算法是一种字符串匹配算法,可以在 O(n+m) 的时间复杂度内实现两个字符串的匹配。 + + diff --git a/1 String/README.md b/1 String/README.md index 07353c5..620f262 100644 --- a/1 String/README.md +++ b/1 String/README.md @@ -25,7 +25,7 @@ ## 滑动窗口 -使用滑动窗口解决字符串问题,代码框架 +使用滑动窗口解决字符串子串问题,代码框架 ``` int left = 0, right = 0; diff --git a/1 String/java String.md b/1 String/java String.md index a837aad..e84a90e 100644 --- a/1 String/java String.md +++ b/1 String/java String.md @@ -3,7 +3,6 @@ Java 中 String 实现。 -``` -``` + diff --git a/2 List/QPS Counter.md b/2 List/QPS Counter.md index 3411dea..570c2c4 100644 --- a/2 List/QPS Counter.md +++ b/2 List/QPS Counter.md @@ -5,3 +5,35 @@ + +```Java + static AtomicInteger qpsCount = 100; //线程安全 + static volatile long lastSenconds = System.currentTimeMillis()/1000; + + //1 计数器 + public static boolean tryAcquire() { + + long current = System.currentTimeMillis()/1000; + if(current == lastSenconds){ + + if (qpsCount-- > 0) {//CAS api + return true; + } else { + //限流 + return false; + } + + } else{//下一个时间窗口 + lastSenconds = current; + qpsCount = 100; + return true; + } + } +``` + + + + + + + diff --git a/2 List/README.md b/2 List/README.md index 458996f..2ff036e 100644 --- a/2 List/README.md +++ b/2 List/README.md @@ -4,6 +4,7 @@ * 双向链表 * 双向循环链表 + ## 链表 ### 应用场景 @@ -11,6 +12,21 @@ * [redis slowlog](Redis%20slowlog.md) +### 链表 VS 数组 + +特点 + +* 数组 : 内存连续, 更好利用局部性原理;内存空间必须一次性分配够,所以说数组如果要扩容,需要重新分配一块更大的空间,再把数据全部复制过去,有界 +* 链表 : 不存在数组的扩容问题, 空间不连续,你无法根据一个索引算出对应元素的地址,所以不能随机访问; 需要前后元素位置的指针,会消耗相对更多的储存空间 + +优缺点: + +* 查询: 数组 O(1), 有序时可以用二分查找; +* 删除: 链表只需要移动指针 O(1) ,数组的话删除元素需要移动后续的元素 O(N) + + + + ### 扩缩容 简单说下编程语言 java, golang中 LinkList的扩缩容的策略。 @@ -23,12 +39,15 @@ java 中扩容,每次扩容新增原先容量的 1/2 这个就不介绍了。重点说下双向链表。 + ## 双向链表 双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。 双向链表克服了单链表中访问某个节点前驱节点(插入,删除操作时),只能从头遍历的问题。 +缺点是: 多了1倍的额外的指针空间大小。 + ``` typedef int Value typedef struct Entry{ @@ -38,6 +57,12 @@ typedef struct Entry{ ``` +应用场景 + +* mysql B+树 叶子节点就使用 双向链表,方便 `age<10` 类似条件查询,或者 倒序查询 如 `order by desc` ,从后向前遍历数据 +* Java AQS 中的等待队列, 是一个双端 双向链表 的结构 (FIFO 结构) + + ## 循环链表 最后一个节点指针指向头节点的链表 @@ -54,3 +79,7 @@ typedef struct Entry{ + + + + diff --git a/2 List/数组.md b/2 List/数组.md new file mode 100644 index 0000000..dc02027 --- /dev/null +++ b/2 List/数组.md @@ -0,0 +1,28 @@ +# 数组 + +数据与链表差异 [参考这里](README.md) + + +## 循环数组 + + +特点 + +* 长度固定,下表不会越界,使用2个指针标识 头尾 下标; 默认都是 0 +* 方便用来实现 栈,队列;比如 队列实现时,新增元素,头下表+1; 删除元素,尾下标+1 + +比如Java的 ArrayBlockingQueue 就是一个 带有 takeIndex 和 putIndex 的环形数组。 + + +缺点:头尾之间的元素不好维护,从中间删除了某个元素,会出现数组空隙。 + + + +数据结构 + +``` +``` + + + + diff --git a/3 Hash Table/LinkedHashMap.md b/3 Hash Table/LinkedHashMap.md index 515225a..4a3db88 100644 --- a/3 Hash Table/LinkedHashMap.md +++ b/3 Hash Table/LinkedHashMap.md @@ -100,6 +100,64 @@ protected boolean removeEldestEntry(Map.Entry eldest) { ``` +基于 LinkedHashMap实现的 LRU Cache + + +```Java +class LRUCache { + int cap; + LinkedHashMap cache = new LinkedHashMap<>(); + public LRUCache(int capacity) { + this.cap = capacity; + } + + //访问元素 + public int get(int key) { + if (!cache.containsKey(key)) { + return -1; + } + // 将 key 变为最近使用 + makeRecently(key); + return cache.get(key); + } + + //添加元素到cache + public void put(int key, int val) { + if (cache.containsKey(key)) { + // 修改 key 的值 + cache.put(key, val); + // 将 key 变为最近使用 + makeRecently(key); + return; + } + + if (cache.size() >= this.cap) { + // 链表头部就是最久未使用的 key + int oldestKey = cache.keySet().iterator().next(); + cache.remove(oldestKey); + } + // 将新的 key 添加链表尾部 + cache.put(key, val); + } + + private void makeRecently(int key) { + int val = cache.get(key); + // 删除 key,重新插入到队尾 + cache.remove(key); + cache.put(key, val); + } +} + +``` + + + + + + + + + diff --git a/3 Hash Table/README.md b/3 Hash Table/README.md index 409a780..0bf1d4f 100644 --- a/3 Hash Table/README.md +++ b/3 Hash Table/README.md @@ -63,6 +63,7 @@ Hash hashCode(char *key){ 当然,还有其他的散列函数,如`平方取中法`, `随机数法`等。 + ## 碰撞解决 diff --git a/4 Tree/2-二叉查找树/二叉查找树.md b/4 Tree/2-二叉查找树/二叉查找树.md index 965a982..6e5ce4f 100644 --- a/4 Tree/2-二叉查找树/二叉查找树.md +++ b/4 Tree/2-二叉查找树/二叉查找树.md @@ -1,13 +1,16 @@ -# 二叉查找树 +# 二叉查找树BST -二叉查找树(Binary search tree),也叫`有序二叉树(Ordered binary tree)`,`排序二叉树(Sorted binary tree)`。是指一个空树或者具有下列性质的二叉树: +二叉查找树(Binary search tree),也叫`有序二叉树(Ordered binary tree)`,`排序二叉树(Sorted binary tree)`。 + +是指一个空树或者具有下列性质的二叉树: 1. 若任意节点的左子树不为空,则左子树上所有的节点值小于它的根节点值 2. 若任意节点的右子树不为空,则右子树上所有节点的值均大于它的根节点的值 3. 任意节点左右子树也为二叉查找树 4. 没有键值(key)相等的节点 -有序的二叉查找树,中序遍历结果是递增的。 + +有序的二叉查找树,中序遍历结果是递增的。 `左小右大` ``` @@ -23,12 +26,15 @@ ``` +### 删除节点 + 删除节点,需要重建排序树 1) 删除节点是叶子节点(分支为0),结构不破坏 2)删除节点只有一个分支(分支为1),结构也不破坏 - 3)删除节点有2个分支,此时删除节点 + 3)删除节点有2个分支,此时删除节点 ; 需要重建树 + 思路一: 选左子树的最大节点,或右子树最小节点替换 ``` @@ -55,6 +61,7 @@ int bisearch_tree_delete(BiSearchTree **tree,ElemType node){ printf("树为空,或想要删除的节点不存在\n"); return -1; } + //该节点为叶子节点,直接删除 if (!target->rChild && !target->lChild) { @@ -86,7 +93,8 @@ int bisearch_tree_delete(BiSearchTree **tree,ElemType node){ target->rChild=target->rChild->rChild; free(del); - } + } + else{ //左右子树均不空,p,t 2个指针一前以后,将左子树最大的节点(肯定是一个最右的节点)替换到删除的节点后,还需要处理左子树最大节点的左子树 BiSearchTree *p=target,*t=target->lChild; diff --git a/4 Tree/4-字典树Trie/README.md b/4 Tree/4-字典树Trie/README.md index cda44ef..6968f78 100644 --- a/4 Tree/4-字典树Trie/README.md +++ b/4 Tree/4-字典树Trie/README.md @@ -1,6 +1,5 @@ # 字典树trie -## trie基本 `字典树`,英文名`Trie树`,Trie一词来自retrieve,发音为/tri:/ “tree”,也有人读为/traɪ/ “try”, 又称`单词查找树` 或 `前缀树`,Trie树,是一种树形结构(多叉树)。 @@ -81,9 +80,12 @@ trie树把每个关键字保存在一条路径上,而不是一个节点中 ## trie树存储结构和基本操作 -最简单实现 ---- 26个字母表 a-z (没有考虑数字,大小写,其他字符如=-*/) -子树用数组存储,浪费空间;如果系统中存在大量字符串,且这些字符串基本没有公共前缀,trie树将消耗大量内存 -如果用链表存储,查询时需要遍历链表,查询效率有所降低 +最简单实现 ---- 26个字母表 `a-z` (没有考虑数字,大小写,其他字符如 `=-*/`) + +trie 树存储结构 + +* 用数组存储,浪费空间;如果系统中存在大量字符串,且这些字符串基本没有公共前缀,trie树将消耗大量内存 +* 用链表存储,查询时需要遍历链表,查询效率有所降低 diff --git a/4 Tree/5-伸展树/伸展树.md b/4 Tree/5-伸展树/伸展树.md index 1a9e070..eaa5d51 100644 --- a/4 Tree/5-伸展树/伸展树.md +++ b/4 Tree/5-伸展树/伸展树.md @@ -1,5 +1,4 @@ - -## 伸展树 (splay tree) +# 伸展树 (splay tree) 伸展树是一种自平衡的二叉排序树。为什么需要这些自平衡的二叉排序树? diff --git a/4 Tree/6-后缀树/后缀树.md b/4 Tree/6-后缀树/后缀树.md index 47e8368..43c5a72 100644 --- a/4 Tree/6-后缀树/后缀树.md +++ b/4 Tree/6-后缀树/后缀树.md @@ -1,5 +1,4 @@ - -## 后缀树(suffix tree) +# 后缀树(suffix tree) ###后缀树的应用 diff --git a/4 Tree/8-堆/Top-K 问题.md b/4 Tree/8-堆/Top-K 问题.md new file mode 100644 index 0000000..326ca38 --- /dev/null +++ b/4 Tree/8-堆/Top-K 问题.md @@ -0,0 +1,20 @@ +# Top-K 问题 + + +问题描述:从arr[1, n]这n个数中,找出最大的k个数,这就是经典的TopK问题 + + +思路 + +1. 排序,全局都排序了,这也是这个方法复杂度非常高的原因 ; [排序算法参考这里](../../6%20Sort/REAME.md) +2. 冒泡排序,局部排序徐,只对最大的k个数排序 +3. [堆排序](./堆.md), 只找最大的k个数,这k个数不需要排序; top-k大 问题就是用 小根堆, 小根堆 固定为 k 个元素大小 , 遍历 k-N (N为所有数据的个数),插入小根堆并调整堆,以保证堆内的k个元素,总是当前最大的k个元素。 + + + + + + + + + diff --git a/4 Tree/8-堆/pq-1.jpg b/4 Tree/8-堆/pq-1.jpg new file mode 100644 index 0000000..e8b9ceb Binary files /dev/null and b/4 Tree/8-堆/pq-1.jpg differ diff --git a/4 Tree/8-堆/pq-1.png b/4 Tree/8-堆/pq-1.png new file mode 100644 index 0000000..78202ff Binary files /dev/null and b/4 Tree/8-堆/pq-1.png differ diff --git a/4 Tree/8-堆/堆.md b/4 Tree/8-堆/堆.md index 16bb213..c6e8d6e 100644 --- a/4 Tree/8-堆/堆.md +++ b/4 Tree/8-堆/堆.md @@ -1,5 +1,201 @@ # 大顶堆 和 小顶堆 +先来了解下 `堆` 结构; 堆也被称为`优先队列`,`二叉堆` ; + +特点 + +* 堆总是一颗完全二叉树树, 使用数组作为其存储结构,因此也叫 `二叉堆`; 用链表存的就叫二叉树了 +* 任一节点小于(或大于)其所有的孩子节点; +* 堆分小根堆和大根堆; 如果根节点大于所有孩子节点,这就是一颗大根堆,也就是根节点是堆上的最大值;如果节点小于所有的子节点,这就是一颗小跟堆,也即是根节点是堆上所有节点的最小值。 +* 小根堆: 每次取出来的元素都是队列中值最小的 + + +`堆用数组来存储`,因为是一颗完全二叉树,i节点的父节点索引就是(i-1)/2, 左右子节点小标是 2i+1,2i+2。 + + +比如小根堆存储示例 + +![小根堆](./pq-1.jpg) + + + +### 应用场景 + +* 优先队列 如iOS中的 NSOperationQueue 就是维护一个优先队列 +* 堆排序 +* [top-K 大(小)](Top-K%20问题.md) , top-k大 问题就是用 小根堆, 小根堆 固定为 k 个元素大小 , 遍历 k-N (N为所有数据的个数),插入小根堆并调整堆,以保证堆内的k个元素,总是当前最大的k个元素。 + + + +### Java PriorityQueue + + +Java PriorityQueue 类就是 通过二叉小顶堆实现 ,也叫优先级队列实现。 有如下特点: + +* 实现 Queue 接口 +* 头部是基于自然排序或基于比较器的排序的最小元素 +* 不是线程安全的,PriorityBlockingQueue在并发环境中使用 + + + +API 如下: + +* boolean add(object):将指定的元素插入此优先级队列。 +* boolean offer(object):将指定的元素插入此优先级队列。 +* boolean remove(object):从此队列中删除指定元素的单个实例(如果存在)。 +* Object poll():检索并删除此队列的*头部*,如果此队列为空,则返回null。 +* Object element():检索但不删除此队列的*头部*,如果此队列为空,则返回null。 +* Object peek():检索但不删除此队列的*头部*,如果此队列为空,则返回null。 +* void clear():从此优先级队列中删除所有元素。 + + +``` +{ + PriorityQueue pq = new PriorityQueue(); + pq.add(new Employee(1L, "AAA", LocalDate.now())); + pq.add(new Employee(4L, "BBB", LocalDate.now())); + pq.add(new Employee(3L, "DDD", LocalDate.now())); + pq.add(new Employee(7L, "GGG", LocalDate.now())); + pq.add(new Employee(2L, "CCC", LocalDate.now())); + + while (true) { + Employee head = pq.poll(); + System.out.println(head); + if (head == null) { + return; + } + } + } + +``` + +输出 + +``` +Employee [id=1, name=AAA, dob=2021-12-22] +Employee [id=2, name=CCC, dob=2021-12-22] +Employee [id=3, name=DDD, dob=2021-12-22] +Employee [id=4, name=BBB, dob=2021-12-22] +Employee [id=7, name=GGG, dob=2021-12-22] +``` + + +如下,使用 PriorityQueue实现的 top-K 大问题,实现如下: + +```Java +/** + * 小根堆实现 + */ + public static int findMaxK(int[] nums, int k) { + PriorityQueue pq = new PriorityQueue<>(k, (a, b) -> (a-b) ); + + for (int i = 0; i > { + // 存储元素的数组 + private Key[] pq; + // 当前 Priority Queue 中的元素个数 + private int N = 0; + + public MaxPQ(int cap) { + // 索引 0 不用,所以多分配一个空间 + pq = (Key[]) new Comparable[cap + 1]; + } + + /* 返回当前队列中最大元素 */ + public Key max() { + return pq[1]; + } + + /* 插入元素 e */ + public void insert(Key e) {...} + + /* 删除并返回当前队列中最大元素 */ + public Key delMax() {...} + + /* 上浮第 k 个元素,以维护最大堆性质 */ + private void swim(int k) {...} + + /* 下沉第 k 个元素,以维护最大堆性质 */ + private void sink(int k) {...} + + /* 交换数组的两个元素 */ + private void exch(int i, int j) { + Key temp = pq[i]; + pq[i] = pq[j]; + pq[j] = temp; + } + + /* pq[i] 是否比 pq[j] 小? */ + private boolean less(int i, int j) { + return pq[i].compareTo(pq[j]) < 0; + } + + /* 还有 left, right, parent 三个方法 */ +} + +``` + + + + + + + + + + + + + + + + + + + diff --git a/4 Tree/9-红黑树 R-B tree/红黑树.md b/4 Tree/9-红黑树 R-B tree/红黑树.md index a33a927..48aa007 100644 --- a/4 Tree/9-红黑树 R-B tree/红黑树.md +++ b/4 Tree/9-红黑树 R-B tree/红黑树.md @@ -1,6 +1,6 @@ # 红黑树 (red-black tree) -红黑树(Red Black Tree) 是一种自平衡二叉查找树。一种特化的AVL树,在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。 它可以在O(log n)时间内做查找,插入和删除。 +红黑树(Red Black Tree) 是一种自平衡二叉查找树。一种特化的[AVL树](../3-平衡树AVL/README.md),在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。 它可以在O(log n)时间内做查找,插入和删除。 它的每个结点都被“着色”为红色或者黑色,这些结点的颜色被用来检测树的平衡性。 @@ -49,3 +49,10 @@ + + + + + + + diff --git a/4 Tree/91-反向索引/反向索引.md b/4 Tree/91-反向索引/反向索引.md deleted file mode 100644 index bcb9b23..0000000 --- a/4 Tree/91-反向索引/反向索引.md +++ /dev/null @@ -1,12 +0,0 @@ -# 反向索引 - -正排索引如 mysql中的索引实现 ,通常使用 B+ 树实现。 反向索引如何来实现呢? - -### 应用场景 - -* ES -* 推荐内容召回 - - - - diff --git a/4 Tree/README.md b/4 Tree/README.md index 08e1edb..6b16a06 100644 --- a/4 Tree/README.md +++ b/4 Tree/README.md @@ -16,16 +16,25 @@ * 赫夫曼编码 Huffman -## [二叉查找树](2-二叉查找树/二叉查找树.md) +## 二叉树 -快速排序就是个二叉树的前序遍历,归并排序就是个二叉树的后序遍历 +[快速排序](../6%20Sort/README.md)就是个二叉树的前序遍历,归并排序就是个二叉树的后序遍历 + + +## [二叉查找树BST](2-二叉查找树/二叉查找树.md) + +有序的二叉树,中序遍历结果是递增的 ## [AVL树](3-平衡树AVL/README.md) +绝对的平衡二叉树; + ## [红黑树](9-红黑树%20R-B%20tree/红黑树.md) +弱平衡二叉树;使用广泛 + ## [字典树trie](4-字典树Trie/README.md) @@ -48,10 +57,6 @@ -## 赫夫曼编码 Huffman - - - diff --git a/4 Tree/huffman tree/赫夫曼编码.md b/4 Tree/huffman tree/赫夫曼编码.md index 8e641a6..c35c115 100644 --- a/4 Tree/huffman tree/赫夫曼编码.md +++ b/4 Tree/huffman tree/赫夫曼编码.md @@ -1,19 +1,24 @@ +# 赫夫曼编码 Huffman -## 赫夫曼编码 Huffman +Huffman在1952年根据香农(Shannon)在1948年和范若(Fano)在1949年阐述的这种编码思想提出了一种不定长编码的方法,也称霍夫曼(Huffman)编码。 -这是一个经典的压缩算法。通过`字符出现的频率`,`优先级`,`二叉树`进行的压缩算法。 +这是一个经典的压缩算法。通过`字符出现的频率`,`优先级`,`二叉树` 进行的压缩算法。 -对一个字符串,计算每个字符出现的次数,把这些字符放到优先队列(priority queue) -这这个priority queue转出二叉树 +对一个字符串,计算每个字符出现的次数, 把这些字符放到优先队列(priority queue); 这个priority queue转出二叉树 需要一个字符编码表来解码,通过二叉树建立huffman编码和解码的字典表 + +举一个例子: + 原始串: 二级制编码: huffman编码: + + ### 存储结构和基本操作 ``` @@ -42,6 +47,7 @@ struct node{ 被编码的文本长度 unsigned int size 字符频率表 unsigned char freqs[NUM_CHARS] + ###解压缩 1. 读取文件头 diff --git a/5 Graph/DFS 和 BFS.md b/5 Graph/DFS 和 BFS.md index cbfbc4a..ed7d73c 100644 --- a/5 Graph/DFS 和 BFS.md +++ b/5 Graph/DFS 和 BFS.md @@ -1,4 +1,17 @@ -# DFS 和 BFS +# DFS 和 BFS 搜索算法 + +DFS: 深度优先搜索,以深度为准则,先一条路走到底,直到达到目标; 没有达到目标又无路可走了,那么则退回到上一步的状态,走其他路。这便是回溯上来。 + +BFS:广度优先搜素,在面临一个路口时,把所有的岔路口都记下来,然后选择其中一个进入,然后将它的分路情况记录下来,然后再返回来进入另外一个岔路,并重复这样的操作。 + + + +> DFS用递归的形式,用到了栈结构,先进后出; BFS选取状态用队列的形式,先进先出。 + + + + + diff --git a/5 Graph/README.md b/5 Graph/README.md index 7fe21e1..0774d79 100644 --- a/5 Graph/README.md +++ b/5 Graph/README.md @@ -9,12 +9,12 @@ ### 应用场景 -工业界有哪些应用场景 +工业界有哪些应用场景? * 匹配,如打车中司乘匹配引擎,如何做到效率最优 -* 并行任务调度,一组任务,任务有优先级,如何合理安排任务调度,在最短时间内完成 -* 导航路径规划,在使用导航软件时,用户在选择一个开始地点和目的地之后导航软件会给出各种如路程最短,不走高速,时长最短等方案 -* 社区发现,在好友关系中,根据社区之间联系或紧密,利用图 louvain 算法或者其他算法对用户进行分群从而达到精准营销,个性化服务等 +* 并行任务调度: 一组任务,任务有优先级,如何合理安排任务调度,在最短时间内完成 +* 导航路径规划:在使用导航软件时,用户在选择一个开始地点和目的地之后导航软件会给出各种如路程最短,不走高速,时长最短等方案 +* 社区发现: 在好友关系中,根据社区之间联系或紧密,利用图 louvain 算法或者其他算法对用户进行分群从而达到精准营销,个性化服务等 * 金融贷后催收:利用图算法找出符合条件的失联人的联系人,从而提高催收失联修复的覆盖率、有效联系率,助力不良资产的回收 * 套汇 @@ -37,16 +37,23 @@ # 图的存储结构 * 对象和指针 -* 矩阵 +* 邻接矩阵 (二维数组) * 邻接表 +图数据结构表示: + +``` + +``` + + ## 图的操作 #### 遍历 -* 广度优先 -* 深度优先 +* 广度优先 BFS +* 深度优先 DFS Dijkstra A* @@ -67,3 +74,6 @@ https://www.jiqizhixin.com/articles/2019-05-16-14 + + + diff --git a/5 Graph/最短路径.md b/5 Graph/最短路径.md index 38713e8..d933843 100644 --- a/5 Graph/最短路径.md +++ b/5 Graph/最短路径.md @@ -1 +1,31 @@ -# 最短路径 \ No newline at end of file +# 最短路径算法 + + +最短路径问题: 寻找图(由结点和路径组成的)中两结点之间的最短路径 + + +实现算法 + +* A* 算法 +* Floyd +* Dijkstra(迪杰斯特拉) +* bellman-ford +* spfa + + +## A* 算法 + +`A*(A-Star)`算法是一种静态路网中求解最短路径最有效的直接搜索方法; 算法中的距离估算值与实际值越接近,最终搜索速度越快。 + + + +## Dijkstra:最短路径算法 + +Dijkstra 解决图中一点到其余各点到最短路径的问题 + +Dijkstra是荷兰的计算机科学家,提出”信号量和PV原语“,"解决哲学家就餐问题",”死锁“也是它提出来的) + + + + + diff --git a/6 Sort/README.md b/6 Sort/README.md index 2099a69..fd0ff97 100644 --- a/6 Sort/README.md +++ b/6 Sort/README.md @@ -3,21 +3,32 @@ **排序的稳定性** 是指对于相等的元素,排序之后,任然保存2个元素的位置没有变,就是稳定的排序,反之就是不稳定排序。 -**交换排序算法** +* 交换排序算法 +* 桶排序 -* 冒泡排序 -* 插入排序 -* 选择排序 -* 希尔排序 -* 快排 : 选最后一个作为pivot(基数), 将数据分为 左边小于 pivot, 右边大于 pivot -* 归并排序 : -* 堆排序 +### 交换排序算法 -**线性排序算法** +* 冒泡排序 :没轮确定一个最大的数排后面 +* 选择排序 :每轮选择最小的数排前面 +* 快排 : 选最后一个作为pivot(基数), 将数据分为 左边小于 pivot, 右边大于 pivot; 分治递归, 二叉树的前序遍历思路 +* 归并排序 : 将无序的数组,分成2个子数组分别排序,然后再merge ; 分治递归, 二叉树的后续遍历思路 +* 插入排序 : +* 希尔排序 : +* [堆排序](../4%20Tree/8-堆/堆.md) : + + +### 线性排序算法 * 桶排序 +### 小结 + +常见的排序算法都是比较排序,比较排序的时间复杂度通常为 O(n^2) 或 O(nlogn) +但是如果带排序的数字有一些特俗性时,我们可以根据这来设计更加优化的排序算法。 + + + # 交换排序算法 排序算法的复杂度由 `比较的次数` 和 `交换的次数` 一起决定。 @@ -31,6 +42,8 @@ 在a【i,n】中最小的元素和 a[i]交换位置。空间复杂度O(1),时间复杂度 O(n^2) +![](./selectsort.gif) + ### 冒泡排序 @@ -40,50 +53,41 @@ 这样,即使是排好序的拿冒泡排序排序,比较的时间复杂度O(n^2) - - - -### 插入排序 - -1. 第一个元素算作已经排好 -2. 取下一个元素,从已经排好的序列元素中,从后向前扫描 -3. 如果排好序的元素大于 新元素,排好序的元素移到下一个位置 -4. 重复3,直到直到最后的插入位置 -5. 重复2 - -类似插入扑克牌的效果 - -最坏的情况: 待排序的是一个逆序排放的数组,这样导致每一轮都要移动元素;此时复杂度是是0(n^2) -最好的情况: 待排序的是一已经顺序排放的数字,此时只需要做一轮比较就够了 0(n)。因此可以看到,**对大部分数据已经有序这样的数组排序,使用`插入排序`非常有优势** - -空间复杂度O(1) - - -### 希尔排序 - -递减增量排序算法,对`插入排序`的改进,实质是分组插入排序,又叫`缩小增量排序` - -1. 先将待排数列分割成若干子序列(增量为m) -2. 对每个子序列使用`插入排序` -3. 减小增量,再排序 -4. 对全体元素做一次`插入排序` - -`希尔排序`提升排序的奥秘就在于`数据元素越有序,使用插入排序效率越高` - +![](./bubblesort.gif) ### 快速排序 +时间复杂度 平均复杂度 O(n * logn) , 最坏 O(n^2) +空间复杂度 +快速排序是对冒泡排序的改进,划分交换排序。 + + 递归一次,pivot 左边都比它小,右边都比它大。这是递归,分治的思想。 -> 快速排序就是个二叉树的前序遍历,归并排序就是个二叉树的后序遍历 +> 快速排序就是个二叉树的前序遍历思路,归并排序就是个二叉树的后序遍历思路 + + +代码框架如下 + +```C +void quicksort(int *a, int left, int right){ + if (left n 个不重复的数,全排列共有 n! 个 +```Java + // 路径:记录在 track 中 + // 选择列表:nums 中不存在于 track 的那些元素 + // 结束条件:nums 中的元素全都在 track 中出现 + void backtrack(int[] nums, LinkedList track) { + // 触发结束条件 + if (track.size() == nums.length) { + res.add(new LinkedList(track)); + return; + } + + for (int i = 0; i < nums.length; i++) { + // 排除不合法的选择 + if (track.contains(nums[i])) + continue; + + // 做选择 + track.add(nums[i]); + + // 进入下一层决策树 + backtrack(nums, track); + // 取消选择 + track.removeLast(); + } + } +``` + ## 八皇后问题 @@ -48,9 +82,5 @@ def backtrack(路径, 选择列表): -#### 其它案例 - -* 迷宫问题 -* 全排列 diff --git a/8 Algorithms Analysis/穷举搜索法.md b/8 Algorithms Analysis/穷举搜索法.md index 783a715..2da99f3 100644 --- a/8 Algorithms Analysis/穷举搜索法.md +++ b/8 Algorithms Analysis/穷举搜索法.md @@ -1,6 +1,4 @@ - - -## 穷举搜索法 +# 穷举搜索法 或者叫蛮力法。对可能的解的众多候选按照某种顺序逐一枚举和检验。典型的问题如选择排序和冒泡排序。 diff --git a/8 Algorithms Analysis/贪心算法.md b/8 Algorithms Analysis/贪心算法.md index da6d18a..400c5dd 100644 --- a/8 Algorithms Analysis/贪心算法.md +++ b/8 Algorithms Analysis/贪心算法.md @@ -1,17 +1,19 @@ - - -## 贪心算法 +# 贪心算法 不追求最优解,只找到满意解。 -#### 赫夫曼编码 - - - #### 其它案例 -找回零钱问题 -装箱问题 +* 跳跃游戏 +* 射击气球 +* 装箱问题 + +## 赫夫曼编码 + + + + + diff --git a/8 Algorithms Analysis/迭代法.md b/8 Algorithms Analysis/迭代法.md index 8352728..41b808d 100644 --- a/8 Algorithms Analysis/迭代法.md +++ b/8 Algorithms Analysis/迭代法.md @@ -1,5 +1,4 @@ - -## 迭代法 +# 迭代法 是一种不断用旧值递推新值的过程,分精确迭代和近视迭代。是用来求方程和方程组近似根的方法。 diff --git a/8 Algorithms Analysis/递归.md b/8 Algorithms Analysis/递归.md index 4834141..761ed1e 100644 --- a/8 Algorithms Analysis/递归.md +++ b/8 Algorithms Analysis/递归.md @@ -1,12 +1,14 @@ # 递归 -递归是一种设计和描述算法的有力工具。 递归算法执行过程分 `递推` 和 `回归` 两个阶段 +递归是一种设计和描述算法的有力工具。 也是回溯法和动态规划的基础。 + +递归算法执行过程分 `递推` 和 `回归` 两个阶段 在 `递推` 阶段,将大的问题分解成小的问题 在 `回归` 阶段,获得最简单问题的解后,逐级返回,依次得到稍微复杂情况的解,知道获得最终的结果 -1) 确定递归公式 -2) 确定边界条件 +1) 确定递归公式 , 比如 斐波那契数列 问题中的 `fib(n)=fib(n-1)+fib(n-2)` +2) 确定边界条件 bad case > 自顶向下的递归,自底向上是迭代 @@ -14,6 +16,70 @@ 递归运行效率较低,因为有函数调用的开销,递归多次也可能造成栈溢出。 +### 递归公式 + +[快排](../6%20Sort/README.md) + +```Java + void quicksort(int *a, int left, int right){ + if (left +``` ## 判断字符串是否是回文 diff --git a/9 Algorithms Job Interview/1.1 字符串-查找.md b/9 Algorithms Job Interview/1.1 字符串-查找.md index c7b6aa9..dc80de2 100644 --- a/9 Algorithms Job Interview/1.1 字符串-查找.md +++ b/9 Algorithms Job Interview/1.1 字符串-查找.md @@ -27,11 +27,14 @@ char char_first_appear_once(const unsigned char *source){ int hash[256]={0}; char *tmp = source; if (tmp == NULL) return '\0'; + + //第一次建立hash表 key为字符,value为出现次数 while(*tmp != '\0'){ hash[*tmp]++; tmp++; } + //第二次扫描找到第一个value为1的key tmp = source; while(*tmp != '\0'){ if (hash[*tmp] == 1) return *tmp; @@ -45,19 +48,6 @@ char char_first_appear_once(const unsigned char *source){ 题目扩展:这里的字符换成整数,整数数量几十TB,海量数据处理,显然hash方法不可能,没有那么大得内容 -## 请编写能直接实现strstr()函数功能的代码。 - -> strstr(str1,str2) 判断str2是否是str1的子串。 - -``` -/* -@ret 有就返回第一次出现子串的地址,否则返回NULL -*/ -char *strstr(const char *source, const char *target){ - -} -``` - ### 字符串中找出连续最长的数字子串 @@ -164,9 +154,68 @@ int lengthOfLongestSubstring(string s) { -## 求最大连续递增数字子串 +## 对称子字符串的最大长度 (最长回文子串) + +题目:输入一个字符串,输出该字符串中对称的子字符串的最大长度。比如输入字符串“google”,由于该字符串里最长的对称子字符串是“goog”,因此输出4。 + +分析:可能很多人都写过判断一个字符串是不是对称的函数,这个题目可以看成是该函数的加强版。 + +``` +int max_symmetrical_char_length(const char *scr); +``` + +* 思路一:蛮力法,3重循环(类似 求子数组的最大和 fmax(i,j)问题), fmax(i,j)区间i,j是最长的对称字符 +* 思路二:遍历所有子串,然后判读是否对称 O(n^2) +* 思路三:有个O(n)复杂度的算法 http://www.cnblogs.com/McQueen1987/p/3559497.html 分析过程如下: + + +``` +int max_symmetrical_char_length(const char *scr){ + +} +``` + + +```Java + public String longestPalindrome(String s) { + String res = ""; + for (int i = 0; i < s.length(); i++) { + // 以 s[i] 为中心的最长回文子串 + String s1 = palindrome(s, i, i); + + // 以 s[i] 和 s[i+1] 为中心的最长回文子串 + String s2 = palindrome(s, i, i + 1); + + // res = longest(res, s1, s2) + res = res.length() > s1.length() ? res : s1; + res = res.length() > s2.length() ? res : s2; + } + return res; + } + + //实现一个函数来寻找最长回文串 + String palindrome(String s, int l, int r) { + // 防止索引越界 + while (l >= 0 && r < s.length() + && s.charAt(l) == s.charAt(r)) { + // 向两边展开 + l--; r++; + } + // 返回以 s[l] 和 s[r] 为中心的最长回文串 + return s.substring(l + 1, r); + } +``` + + +## 最小覆盖子串 + +(难度 hard) `滑动窗口` + +给定 字符串 s, t ; 在字符串 s 里找出 包含 t 所有字母的最小子串 +eg: s = "ADOBECODEBANC" t = "ABC" 输出: "BANC" + + -如“ads3sl456789DF3456ld345AA”中的“456789”就是所求。这道题在上一道题目的基础上增加了数字要递增的条件。思路跟上面差不多,碰到不递增的数字就相当于第二个数字串了。 @@ -193,24 +242,22 @@ int longest_common_subsequence(const char *s1,const char *s2, char *common){ ``` -## 对称子字符串的最大长度 -题目:输入一个字符串,输出该字符串中对称的子字符串的最大长度。比如输入字符串“google”,由于该字符串里最长的对称子字符串是“goog”,因此输出4。 +## 求最大连续递增数字子串 -分析:可能很多人都写过判断一个字符串是不是对称的函数,这个题目可以看成是该函数的加强版。 +如“ads3sl456789DF3456ld345AA”中的“456789”就是所求。这道题在上一道题目的基础上增加了数字要递增的条件。思路跟上面差不多,碰到不递增的数字就相当于第二个数字串了。 + + +## 请编写能直接实现strstr()函数功能的代码。 + +> strstr(str1,str2) 判断str2是否是str1的子串。 ``` -int max_symmetrical_char_length(const char *scr); -``` - -* 思路一:蛮力法,3重循环(类似 求子数组的最大和 fmax(i,j)问题), fmax(i,j)区间i,j是最长的对称字符 -* 思路二:遍历所有子串,然后判读是否对称 O(n^2) -* 思路三:有个O(n)复杂度的算法 http://www.cnblogs.com/McQueen1987/p/3559497.html 分析过程如下: - - -``` -int max_symmetrical_char_length(const char *scr){ - +/* +@ret 有就返回第一次出现子串的地址,否则返回NULL +*/ +char *strstr(const char *source, const char *target){ + } ``` diff --git a/9 Algorithms Job Interview/1.3 字符串-修改.md b/9 Algorithms Job Interview/1.3 字符串-修改.md index 4b18a11..0fcf775 100644 --- a/9 Algorithms Job Interview/1.3 字符串-修改.md +++ b/9 Algorithms Job Interview/1.3 字符串-修改.md @@ -79,22 +79,6 @@ char *revert_by_word(char *source){ 用C语言实现一个revert函数,它的功能是将输入的字符串在原串上倒序后返回。 -## 左旋转字符串 - ->字符串的左旋转操作:把字符串前面的若干个字符移动到字符串的尾部。 - -如把`字符串abcdef`左旋转2位得到`字符串cdefab` 。请实现字符串左旋转的函数。要求时间对长度为n的字符串操作的复杂度为O(n),辅助内存为O(1)。 - -``` -char *left_rotate(char *str,int offset){ - -} -``` - -思路: 我们可以abcdef分成两部分,ab和cdef,内部逆序以后,整体再次逆序,就可以得到想要的结果了。 -也就是跟上面的问题是同样的问题。 - - ## 替换空格 @@ -138,6 +122,23 @@ char *replace_blank(char *source){ ``` +## 左旋转字符串 + +>字符串的左旋转操作:把字符串前面的若干个字符移动到字符串的尾部。 + +如把`字符串abcdef`左旋转2位得到`字符串cdefab` 。请实现字符串左旋转的函数。要求时间对长度为n的字符串操作的复杂度为O(n),辅助内存为O(1)。 + +``` +char *left_rotate(char *str,int offset){ + +} +``` + +思路: 我们可以abcdef分成两部分,ab和cdef,内部逆序以后,整体再次逆序,就可以得到想要的结果了。 +也就是跟上面的问题是同样的问题。 + + + ## 字符串原地压缩 diff --git a/9 Algorithms Job Interview/1.4 字符串-排序.md b/9 Algorithms Job Interview/1.4 字符串-排序.md index 9dd1338..9bc9900 100644 --- a/9 Algorithms Job Interview/1.4 字符串-排序.md +++ b/9 Algorithms Job Interview/1.4 字符串-排序.md @@ -2,7 +2,6 @@ - ## 小写字母排在大写字母的前面 有一个由大小写组成的字符串,现在需要对他进行修改,将其中的所有小写字母排在大写字母的前面(大写或小写字母之间不要求保持原来次序),如有可能尽量选择时间和空间效率高的算法 c语言函数原型: diff --git a/9 Algorithms Job Interview/2 链表.md b/9 Algorithms Job Interview/2 链表.md index 4cf7da9..e1fe717 100644 --- a/9 Algorithms Job Interview/2 链表.md +++ b/9 Algorithms Job Interview/2 链表.md @@ -10,12 +10,14 @@ * 链表中是否有环 -链表结点定义如下: +链表结点定义如下: +``` struct ListNode { int m_nKey; ListNode* m_pNext; }; +``` @@ -110,6 +112,15 @@ struct ComplexNode 请完成函数`ComplexNode* Clone(ComplexNode* pHead)`,以复制一个复杂链表。 +这个题目难点在于: + + + + + + + + diff --git a/9 Algorithms Job Interview/4.2 数值-指数.md b/9 Algorithms Job Interview/4.2 数值-指数.md index 371b804..247983c 100644 --- a/9 Algorithms Job Interview/4.2 数值-指数.md +++ b/9 Algorithms Job Interview/4.2 数值-指数.md @@ -11,6 +11,19 @@ 不需要考虑溢出。 + +用例: + +``` +2^3 = 8 +0^3 = 0 +2^0 = 1 +2^-3 = 1/(2^3) = 0.125 +0^-3 +``` + + + 分析:这是一道看起来很简单的问题。可能有不少的人在看到题目后30秒写出如下的代码: ``` double Power(double base, int exponent) @@ -35,15 +48,6 @@ double Power(double base, int exponent) 2^1 = 2 ``` -用例: - -``` -2 3 = 8 -0 3 = 0 -2 0 = 1 -2 -3 = 1/(2^3) = 0.125 -0 -3 -``` 使用递归实现的代码示例: @@ -62,6 +66,25 @@ double Power(double base, unsigned int exponent) ``` +## Sqrt(x) + +给你一个非负整数 x ,计算并返回 x 的 算术平方根 。由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。 + + +示例 1: +``` +输入:x = 4 +输出:2 +``` +示例 2: +``` +输入:x = 8 +输出:2 +解释:8 的算术平方根是 2.82842..., 由于返回类型是整数,小数部分将被舍去。 +``` + + + ## 求根号2的值 并且按照我的需要列出指定小数位,比如根号2是1.141 我要列出1位小数就是1.1 2位就是1.14, 1000位就是1.141...... 等。。 diff --git a/9 Algorithms Job Interview/5 数组数列问题.md b/9 Algorithms Job Interview/5 数组数列问题.md index 65b823d..e2b4756 100644 --- a/9 Algorithms Job Interview/5 数组数列问题.md +++ b/9 Algorithms Job Interview/5 数组数列问题.md @@ -80,8 +80,6 @@ int fib(int n) { - - ## 递减数列左移后的数组中找数 一个数组是由一个递减数列左移若干位形成的,比如{4,3,2,1,6,5} diff --git a/9 Algorithms Job Interview/5.1 数列-排序.md b/9 Algorithms Job Interview/5.1 数列-排序.md index c4d2950..b01a548 100644 --- a/9 Algorithms Job Interview/5.1 数列-排序.md +++ b/9 Algorithms Job Interview/5.1 数列-排序.md @@ -25,8 +25,6 @@ void sort(int * a, int length) `动态规划` - - ## 扑克牌的顺子 diff --git a/9 Algorithms Job Interview/5.2 数列-nsum问题.md b/9 Algorithms Job Interview/5.2 数列-nsum问题.md index 65ea410..d76c014 100644 --- a/9 Algorithms Job Interview/5.2 数列-nsum问题.md +++ b/9 Algorithms Job Interview/5.2 数列-nsum问题.md @@ -81,7 +81,6 @@ int findaddends(int *data,int length,int sum,int *a,int *b); - ## 求子数组的最大和(最大字段和) 输入一个整形数组,数组里有正数也有负数。 @@ -91,7 +90,7 @@ int findaddends(int *data,int length,int sum,int *a,int *b); 例如输入的数组为`1, -2, 3, 10, -4, 7, 2, -5` ,和最大的子数组为`3, 10, -4, 7, 2` ,因此输出为该子数组的和18。 1. 蛮力法 fmax(i,j) 找出最大的值,3重循环 ,复杂度 0(n^3) -2. maxendindhere保存当前累加的和,如果<0,就把maxendinghere清零 , max保存最终的最大和 +2. `动态规划` maxendindhere保存当前累加的和,如果<0, 就把maxendinghere清零 , max保存最终的最大和; 如果都是负数 ``` int maxSumOfVector(int *data,int length){ diff --git a/9 Algorithms Job Interview/5.4 数列-查找.md b/9 Algorithms Job Interview/5.4 数列-查找.md index f1763fa..24cf68b 100644 --- a/9 Algorithms Job Interview/5.4 数列-查找.md +++ b/9 Algorithms Job Interview/5.4 数列-查找.md @@ -87,13 +87,64 @@ topMaxK(int *a,int length, int k); ## 找第k大的数 -比如 1,2,3,4,5,6,7和8这8个数字,低3大的数字是 6 +比如 `1,2,3,4,5,6,7,8 `这 8个数字,第3大的数字是 6 ``` int topK(int * a, int length, int k) ``` +```Java +//冒泡实现 + public int findK(int[] nums, int k){ + //base case + if (nums== null || nums.length < k) { + return -1; + } + + for(int i=1; i<=k; i++){ + for(int j=0; j nums[next]){ + int tmp = nums[j]; + nums[j] = nums[next]; + nums[next] = tmp; + } + } + } + + return nums[nums.length-k]; + } +``` + + + +```Java +/** + * 小根堆实现 + */ + public static int findMaxK(int[] nums, int k) { + PriorityQueue pq = new PriorityQueue<>(k, (a, b) -> (a-b) ); + + for (int i = 0; i window = new HashMap(); + + while (right < s.length) { + Character c = s[right]; + + //窗口变大 + right++; + window.put(c, window.getOrDefault(c, 0) + 1); //java写的麻烦,不一定记得这个api + + if (window.get(c) > 1) { + //判断左侧窗口是否要收缩 + char d = s[left]; + + left ++; + window.put(d, window.get(d)-1); + } + + // 在这里更新答案 + res = res > (right-left) ? res: (right-left); + + } + return res; +} +``` + + +### 排序算法 + +[排序算法](../6%20Sort/README.md) + +```Java + +#快排 quickSort +void quicksort(int[] nums, int left, int right){ + if (left