diff --git a/.gitignore b/.gitignore index 87513ea..75a69c0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,4 @@ -#### - *.gitignore *.out *.dSYM @@ -8,10 +6,9 @@ *.dat *.txt *ipslice* +*.log - - - -##### +cmake-build-debug +*.idea diff --git a/0 Numeral/base58.md b/0 Numeral/base58.md new file mode 100644 index 0000000..4316c6d --- /dev/null +++ b/0 Numeral/base58.md @@ -0,0 +1,20 @@ +# base58 + +btc中 base58编码表是 + +``` +123456789 abcdefg hijk mn opqrst uvwxyz ABCDEFG HJ KLMN PQRST UVWXYZ +``` + + +相比 base64 去掉了 6个字符, 数字0, 大写字母 O, 大写字母 I (小写i) , 小写字母 l (大写L), 以及 + 和 / + + + +### 编码实现 + +``` + +``` + + diff --git "a/0 Numeral/\346\225\260\345\200\274.md" "b/0 Numeral/\346\225\260\345\200\274.md" new file mode 100644 index 0000000..1a43fa5 --- /dev/null +++ "b/0 Numeral/\346\225\260\345\200\274.md" @@ -0,0 +1,20 @@ +# 数值 + + +`n&(n-1)` 作用是消除数字 n 的二进制表示中的最后一个 1 + + +一个数和它本身做异或运算(^)结果为 0,即 `a ^ a = 0` + + +不用临时变量交换两个数 + +``` +int a = 1, b = 2; +a ^= b; +b ^= a; +a ^= b; +// 现在 a = 2, b = 1 +``` + + 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/String/README.md b/1 String/README.md similarity index 50% rename from String/README.md rename to 1 String/README.md index 72f6792..620f262 100644 --- a/String/README.md +++ b/1 String/README.md @@ -1,5 +1,4 @@ - -## 字符串 +# 字符串 字符串在计算机中的应用非常广泛,这里讨论有关字符串的最重要的算法: @@ -15,6 +14,36 @@ * 游程编码 +[Java中 String实现 参考这里](java%20String.md) + +## 查找 + +* KMP +* BM + + +## 滑动窗口 + + +使用滑动窗口解决字符串子串问题,代码框架 +``` +int left = 0, right = 0; + +while (right < s.size()) { + // 增大窗口 + window.add(s[right]); + right++; + + while (window needs shrink) { + // 缩小窗口 + window.remove(s[left]); + left++; + } +} +``` + + + ## 参考 《Algorithms》 diff --git a/1 String/java String.md b/1 String/java String.md new file mode 100644 index 0000000..e84a90e --- /dev/null +++ b/1 String/java String.md @@ -0,0 +1,8 @@ +# java String + +Java 中 String 实现。 + + + + + diff --git a/2 List/QPS Counter.md b/2 List/QPS Counter.md new file mode 100644 index 0000000..570c2c4 --- /dev/null +++ b/2 List/QPS Counter.md @@ -0,0 +1,39 @@ +# QPS Counter + +统计各接口QPS计数 +使用一个双向循环链表结构 + + + + +```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 new file mode 100644 index 0000000..2ff036e --- /dev/null +++ b/2 List/README.md @@ -0,0 +1,85 @@ +# 链表 + +* 链表 +* 双向链表 +* 双向循环链表 + + +## 链表 + +### 应用场景 + +* [redis slowlog](Redis%20slowlog.md) + + +### 链表 VS 数组 + +特点 + +* 数组 : 内存连续, 更好利用局部性原理;内存空间必须一次性分配够,所以说数组如果要扩容,需要重新分配一块更大的空间,再把数据全部复制过去,有界 +* 链表 : 不存在数组的扩容问题, 空间不连续,你无法根据一个索引算出对应元素的地址,所以不能随机访问; 需要前后元素位置的指针,会消耗相对更多的储存空间 + +优缺点: + +* 查询: 数组 O(1), 有序时可以用二分查找; +* 删除: 链表只需要移动指针 O(1) ,数组的话删除元素需要移动后续的元素 O(N) + + + + +### 扩缩容 + +简单说下编程语言 java, golang中 LinkList的扩缩容的策略。 + +java 中扩容,每次扩容新增原先容量的 1/2 +``` + int newCapacity = oldCapacity + (oldCapacity >> 1); +``` + +这个就不介绍了。重点说下双向链表。 + + + +## 双向链表 + +双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。一般我们都构造双向循环链表。 + +双向链表克服了单链表中访问某个节点前驱节点(插入,删除操作时),只能从头遍历的问题。 + +缺点是: 多了1倍的额外的指针空间大小。 + +``` +typedef int Value +typedef struct Entry{ + struct Entry *next,*prev; + Value value; +}DoubleLink; + +``` + +应用场景 + +* mysql B+树 叶子节点就使用 双向链表,方便 `age<10` 类似条件查询,或者 倒序查询 如 `order by desc` ,从后向前遍历数据 +* Java AQS 中的等待队列, 是一个双端 双向链表 的结构 (FIFO 结构) + + +## 循环链表 + +最后一个节点指针指向头节点的链表 + +[QPS 计数器实现](QPS%20Counter.md) + + +## 双向循环链表 + + + + + + + + + + + + diff --git a/2 List/Redis slowlog.md b/2 List/Redis slowlog.md new file mode 100644 index 0000000..21472d4 --- /dev/null +++ b/2 List/Redis slowlog.md @@ -0,0 +1,66 @@ +# Redis slowlog + + +redis中的slowlog使用链表来保存 + + +``` +struct redisServer { + + // ... + + + // 下一条慢查询日志的 ID + long long slowlog_entry_id; + + + // 保存了所有慢查询日志的链表 + list *slowlog; + + + // 服务器配置 slowlog-log-slower-than 选项的值 + long long slowlog_log_slower_than; + + + // 服务器配置 slowlog-max-len 选项的值 + unsigned long slowlog_max_len; + + + // ... + +}; +``` + + +一条slowlog entry标识 + +``` +typedef struct slowlogEntry { + + + // 唯一标识符 + long long id; + + + // 命令执行时的时间,格式为 UNIX 时间戳 + time_t time; + + + // 执行命令消耗的时间,以微秒为单位 + long long duration; + + + // 命令与命令参数 + robj **argv; + + + // 命令与命令参数的数量 + int argc; + + +} slowlogEntry; + +``` + + + diff --git "a/2 List/\346\225\260\347\273\204.md" "b/2 List/\346\225\260\347\273\204.md" new file mode 100644 index 0000000..dc02027 --- /dev/null +++ "b/2 List/\346\225\260\347\273\204.md" @@ -0,0 +1,28 @@ +# 数组 + +数据与链表差异 [参考这里](README.md) + + +## 循环数组 + + +特点 + +* 长度固定,下表不会越界,使用2个指针标识 头尾 下标; 默认都是 0 +* 方便用来实现 栈,队列;比如 队列实现时,新增元素,头下表+1; 删除元素,尾下标+1 + +比如Java的 ArrayBlockingQueue 就是一个 带有 takeIndex 和 putIndex 的环形数组。 + + +缺点:头尾之间的元素不好维护,从中间删除了某个元素,会出现数组空隙。 + + + +数据结构 + +``` +``` + + + + diff --git "a/2 List/\346\235\241\344\273\266\350\241\250\350\276\276\345\274\217.md" "b/2 List/\346\235\241\344\273\266\350\241\250\350\276\276\345\274\217.md" new file mode 100644 index 0000000..0ab5558 --- /dev/null +++ "b/2 List/\346\235\241\344\273\266\350\241\250\350\276\276\345\274\217.md" @@ -0,0 +1,13 @@ +# 条件表达式 + + +### 应用场景 + +* SQL 语句中的条件 +* 广告系统中配置定向的过滤条件 +* 编译器语法解析 + + + +DNF 析取范式 + diff --git a/2 Queue/README.md b/2 Queue/README.md new file mode 100644 index 0000000..5a2334d --- /dev/null +++ b/2 Queue/README.md @@ -0,0 +1,6 @@ +# 队列 Queue + + + + + diff --git a/2 Queue/ZipList.md b/2 Queue/ZipList.md new file mode 100644 index 0000000..3763ad2 --- /dev/null +++ b/2 Queue/ZipList.md @@ -0,0 +1,30 @@ +# ZipList + + +ZipList 压缩列表 redis 中的 sset(有序集合) 使用跳表来实现,为什么不是用红黑树,而是跳表实现sset,带着这样的疑问,有了本文。 + + +## 理解 + + +有哪些应用场景? + + + +#### 为什么使用 + + + + +## 结构 + + + + +## 插入,删除,查找 实现 + + + + + + diff --git a/2 Queue/skip-list.md b/2 Queue/skip-list.md new file mode 100644 index 0000000..4e936d4 --- /dev/null +++ b/2 Queue/skip-list.md @@ -0,0 +1,25 @@ +# skip-list + +skip-list 跳表。 redis 中的 sset(有序集合) 使用跳表来实现,为什么不是用红黑树,而是跳表实现sset,带着这样的疑问,有了本文。 + + +## 跳表理解 + + +有哪些应用场景? + + +## 跳表的结构 + + + + +## 插入,删除,查找 实现 + + + +## sset 为什么使用 跳表 + + + + diff --git a/3 Hash Table/HashMap in Golang.md b/3 Hash Table/HashMap in Golang.md new file mode 100644 index 0000000..bbcf5fe --- /dev/null +++ b/3 Hash Table/HashMap in Golang.md @@ -0,0 +1,3 @@ +# HashMap in Golang + +java 中 hashmap的实现原理 diff --git a/3 Hash Table/HashMap in Java.md b/3 Hash Table/HashMap in Java.md new file mode 100644 index 0000000..30ca859 --- /dev/null +++ b/3 Hash Table/HashMap in Java.md @@ -0,0 +1,399 @@ +# HashMap in Java + +java 中 hashmap的实现原理。 + +[红黑树参考这里](../4%20Tree/9-红黑树%20R-B%20树) + +## 数据结构 + +* HashMap底层实现, hashmap的存储结构和操作? +* hash冲突如何解决(链表和红黑树)? 为什么hashmap中的链表需要转成红黑树? + + +好的哈希函数会尽可能地保证 计算简单和散列地址分布均匀,但是,再好的哈希函数也不能保证得到的存储地址绝对不发生冲突。 + + + +``` +public class HashMap extends AbstractMap + implements Map, Cloneable, Serializable { + + transient int size; //当前元素个数 + + int threshold; //扩容时机是: 当前容量大于等于 capacity * load factor + + final float loadFactor; //默认 0.75 , 空间使用 75% 时开始扩容 + + static final int TREEIFY_THRESHOLD = 8; //将链表转换为红黑树的阈值 + + static final int UNTREEIFY_THRESHOLD = 6; //将红黑树转换为链表的阈值 + + //1.7结构 + static class Node implements Map.Entry { + final int hash; + final K key; + V value; + Node next;//链表结构 + ... + } + + //1.8结构 + static final class TreeNode extends LinkedHashMap.Entry { + TreeNode parent; // red-black tree links + TreeNode left; + TreeNode right; + TreeNode prev; // needed to unlink next upon deletion + boolean red; + } + + public V put(K key, V value) { + return putVal(hash(key), key, value, false, true); + } + + public V get(Object key) { + Node e; + return (e = getNode(hash(key), key)) == null ? null : e.value; + } + + //hash 算法, 根据 key 的 hashCode() 计算而来 + static final int hash(Object key) { + int h; + return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); + } + +} +``` + +table 在第一次put的时候初始化,length默认16, threshold 12 (0.75x16) , 也就是第 13个 元素 put 的时候开始扩容 + +1.7 的实现是 数组+链表; 1.8 新增了红黑树,提高查询效率 + + +### get(key) 方法 + +``` +public V get(Object key) { + Node e; + return (e = getNode(hash(key), key)) == null ? null : e.value; +} +final Node getNode(int hash, Object key) { + Node[] tab; Node first, e; int n; K k; + if ((tab = table) != null && (n = tab.length) > 0 && + (first = tab[(n - 1) & hash]) != null) { + if (first.hash == hash && // always check first node + ((k = first.key) == key || (key != null && key.equals(k)))) + return first; + if ((e = first.next) != null) { + if (first instanceof TreeNode)//红黑树 查找 + return ((TreeNode)first).getTreeNode(hash, key); + do {//按照链表结构遍历查找 + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k)))) + return e; + } while ((e = e.next) != null); + } + } + return null; +} + +``` + + +``` +//计算hash值 +//这是一个神奇的函数,用了很多的异或,移位等运算,对key的hashcode进一步进行计算以及二进制位的调整等来保证最终获取的存储位置尽量分布均匀 +final int hash(Object k) { + int h = hashSeed; + if (0 != h && k instanceof String) { + return sun.misc.Hashing.stringHash32((String) k); + } + + h ^= k.hashCode(); + + h ^= (h >>> 20) ^ (h >>> 12); + return h ^ (h >>> 7) ^ (h >>> 4); + } +``` + +### put(key, value) 方法 + + +hash存储的过程是: key -> hashcode -> hash -> indexFor() + +``` +public V put(K key, V value) { + return putVal(hash(key), key, value, false, true); +} + + final V putVal(int hash, K key, V value, boolean onlyIfAbsent, + boolean evict) { + + Node[] tab; Node p; int n, i; + if ((tab = table) == null || (n = tab.length) == 0) + n = (tab = resize()).length; + if ((p = tab[i = (n - 1) & hash]) == null)//当前桶为空,没有hash冲突 + tab[i] = newNode(hash, key, value, null); + else { + Node e; K k; + if (p.hash == hash && + ((k = p.key) == key || (key != null && key.equals(k))))//当前桶中的 key、key 的 hashcode 与写入的 key 相等 + e = p; + else if (p instanceof TreeNode)//当前桶为红黑树,那按照红黑树的方式写入数据 + e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value); + else {//链表,就需要将当前的 key、value 封装成一个新节点写入到当前桶的后面 + for (int binCount = 0; ; ++binCount) { + if ((e = p.next) == null) { + p.next = newNode(hash, key, value, null); + if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st + treeifyBin(tab, hash);//判断当前链表的大小是否大于预设的阈值,大于时就要转换为红黑树 + break; + } + if (e.hash == hash && + ((k = e.key) == key || (key != null && key.equals(k))))//在遍历过程中找到 key 相同时直接退出遍历 + break; + p = e; + } + } + if (e != null) { // existing mapping for key + V oldValue = e.value; + if (!onlyIfAbsent || oldValue == null) + e.value = value; + afterNodeAccess(e); + return oldValue; + } + } + ++modCount; + if (++size > threshold)//最后判断是否需要进行扩容 + resize(); + afterNodeInsertion(evict); + return null; + } + +``` + + +``` +//返回数组下标 +static int indexFor(int h, int length) { + return h & (length-1); +} +``` + +这里用的位运行,而不是取模操作; 位运算性能更高。 + + + + + + +## Hash冲突 + +HashMap是怎么处理hash碰撞的? + +使用拉链法,为了提高链表查询效率,当桶对应的链表长度大于8的时候,转为红黑树。 + +## 扩容 + +初始化容量, 默认结果是 16 + +``` +static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16, 为啥用位运算呢?直接写16不好么? +``` + +### 为什么需要扩容? + +主要为缓解哈希冲突造成的外挂链表太长,造成查询性能低下。 + + +### HashMap的扩容方式? 负载因子是多少? 扩容时机?什么时候会触发扩容? + +HashMap 中 `final float loadFactor` , loadFactor 默认 0.75 , 也就是达到容量的 75%时就会开始扩容。 + +那么问题来了,扩容到多大? 看 resize() 方法 + +``` +final Node[] resize() { + Node[] oldTab = table; + int oldCap = (oldTab == null) ? 0 : oldTab.length; + int oldThr = threshold; + int newCap, newThr = 0; + if (oldCap > 0) { + if (oldCap >= MAXIMUM_CAPACITY) { + threshold = Integer.MAX_VALUE; + return oldTab; + } + else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && + oldCap >= DEFAULT_INITIAL_CAPACITY) + newThr = oldThr << 1; // double threshold + } + else if (oldThr > 0) // initial capacity was placed in threshold + newCap = oldThr; + else { // zero initial threshold signifies using defaults + newCap = DEFAULT_INITIAL_CAPACITY; + newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); + } + if (newThr == 0) { + float ft = (float)newCap * loadFactor; + newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? + (int)ft : Integer.MAX_VALUE); + } + threshold = newThr; + + //创建一个新的 newCap 大小的数组容器,调整table + @SuppressWarnings({"rawtypes","unchecked"}) + Node[] newTab = (Node[])new Node[newCap]; + table = newTab; + if (oldTab != null) { + for (int j = 0; j < oldCap; ++j) { + Node e; + if ((e = oldTab[j]) != null) {//有数据的桶才需要调整 + oldTab[j] = null; + if (e.next == null)//没有链表结构 + newTab[e.hash & (newCap - 1)] = e; + else if (e instanceof TreeNode) + ((TreeNode)e).split(this, newTab, j, oldCap); + else { // preserve order + Node loHead = null, loTail = null; + Node hiHead = null, hiTail = null; + Node next; + do { + next = e.next; + if ((e.hash & oldCap) == 0) {//hash值第 N+1 位是否为0 + if (loTail == null) + loHead = e; + else + loTail.next = e; + loTail = e; + } + else { + if (hiTail == null) + hiHead = e; + else + hiTail.next = e; + hiTail = e; + } + } while ((e = next) != null); + + if (loTail != null) { + loTail.next = null; + newTab[j] = loHead; + } + if (hiTail != null) { + hiTail.next = null; + newTab[j + oldCap] = hiHead; + } + } + } + } + } + return newTab; + } +``` + +扩容到了2倍 `newThr = oldThr << 1; ` + + + +### 扩容后元素怎么重排到新的容器中,直接复制拷贝可以吗? + +扩容会 rehash,复制数据等耗时操作。 + + +* hashmap扩容时每个entry需要再计算一次hash吗? + +不需要,但是需要重新调整桶的位置 `newTab[e.hash & (newCap - 1)] = e;` + + +* 扩容时避免rehash的优化 : cap 为 2 的次幂,rehash也是 cap * 2 , 这样可以 `e.hash & (newCap - 1)` 计算时,有较少的 key 产生移动,hash值 高位 1 的 才需要移动 + + + + +## 问题 + + +### JDK7 和 8 HashMap 有什么区别? + +* JDK8 实现引入红黑树,优化链表过长的查询效率;当链表长度大于8是链表的存储结构会被修改成红黑树的形式, 查询效率从O(N)提升到O(logN)。链表长度小于6时,红黑树的方式退化成链表。 +* 1.7 采用头插法, 链表插入是从链表头部插入 ; 1.8采用尾插法, 因此在resize的时候仍然保持原来的顺序; 解决并发多线程中出出现链表成环的问题 ; (hashmap本来就是非线程安全的,为啥要在多线程中使用hashmap) + + + +### 链表上使用的头插还是尾插方式? + +1.7 采用头插法,1.8采用尾插法; 解决多线程中出出现链表成环的问题, 因此在resize的时候仍然保持原来的顺序 + + +### 多线程下死循环问题 + + +* hashmap扩容会引发什么问题,线上是否出现过类似的问题?如何避免扩容引发的问题? +* jdk1.8之前并发操作hashmap时为什么会有死循环的问题? + +HashMap 在并发场景下,容易出现死循环 + +``` +final HashMap map = new HashMap(); +for (int i = 0; i < 1000; i++) { + new Thread(new Runnable() { + @Override + public void run() { + map.put(UUID.randomUUID().toString(), ""); + } + }).start(); +} +``` + +在 HashMap 扩容的时候会调用 resize() 方法,就是这里的并发操作容易在一个桶上形成环形链表;这样当获取一个不存在的 key 时,计算出的 index 正好是环形链表的下标就会出现死循环。程序临床反应就是 CPU 飙高, 这时候应该使用线程安全的HashMap,也就是 ConcurrentHashMap。 + + + + +### hashmap的数组长度为什么要保证是2的幂? + + +https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4631373 提出, 整数的除法(/)和 取模(%) 运算 性能比 位与操作(&) 慢10倍。 + + +`h & (length-1)` 和 `h % length` 结果一样 + +举个例子 +``` +hashcode 311 对应的二进制是(1 0011 0111) +length 16 对应的二进制是(1 0000) , length-1 就是 (0 1111) + +h & (length-1)` 就是取 hashcode 的低 4位 + +``` + +length 保持为 2 的幂, 那么length-1就会变成一个mask, 它会将hashcode低位取出来,hashcode的低位实际就是余数,和取余操作相比,与操作会将性能提升很多。 + +另外,hash扩容时 rehash 操作,只有 hash二进制 高位是 1 的hash key 需要 移动到新的 slot (pos + oldCap), 高位是 0 的 key 不需要移动 + +![hashmap rehash](https://pic3.zhimg.com/80/v2-ed0ca17db342562dfc18434d12227be2_720w.jpg) + + + +### 重写equals方法需同时重写hashCode方法 + + +``` +public static void main(String []args){ + HashMap map = new HashMap(); + Person person = new Person(1234,"乔峰"); + //put到hashmap中去 + map.put(person,"天龙八部"); + //get取出,从逻辑上讲应该能输出“天龙八部” + System.out.println("结果:"+map.get(new Person(1234,"萧峰"))); +} +``` + +hashCode()的默认行为是对堆上的对象产生独特值。因此 map.get(object) 的时候,不同的 object 的hashcode 不一样,自然结果就为null + + + + + + + + diff --git a/Hash Table/HashTable.c b/3 Hash Table/HashTable.c similarity index 100% rename from Hash Table/HashTable.c rename to 3 Hash Table/HashTable.c diff --git a/3 Hash Table/LinkedHashMap.md b/3 Hash Table/LinkedHashMap.md new file mode 100644 index 0000000..4a3db88 --- /dev/null +++ b/3 Hash Table/LinkedHashMap.md @@ -0,0 +1,166 @@ +# LinkedHashMap + +java 中 LinkedHashmap的实现原理。LinkedHashmap继承自 HashMap。 + +HashMap是无序的,迭代访问顺序并不一定与插入(put)顺序一致。LinkedHashMap 是有序的, 迭代顺序与插入顺序一致,这种叫做 插入有序。 + + +``` +//插入有序 + Map linkedHashMap = new LinkedHashMap<>(); + linkedHashMap.put("name1", "josan1"); + linkedHashMap.put("name2", "josan2"); + linkedHashMap.put("name3", "josan3"); + Set> set = linkedHashMap.entrySet(); + Iterator> iterator = set.iterator(); + while(iterator.hasNext()) { + Entry entry = iterator.next(); + String key = (String) entry.getKey(); + String value = (String) entry.getValue(); + System.out.println("key:" + key + ",value:" + value); + } + +output: +key:name1, value:josan1 +key:name2, value:josan2 +key:name3, value:josan3 +``` + + +#### 特点 + +* 维护一个所有entry的双向链表 +* 构造函数 有一个 accessOrder 参数,控制访问顺序 (插入顺序 和 访问顺序); +* 访问顺序的意思是,当有一个entry被访问以后,这个entry就被移动到链表的表尾。这个特性非常适合 LRU 缓存 (最近最少使用); + + +#### 引用场景 + +* LUR 缓存 (最近最少使用) + + +## 原理 + + +``` +public class LinkedHashMap + extends HashMap + implements Map { + + static class Entry extends HashMap.Node { + Entry before, after; + Entry(int hash, K key, V value, Node next) { + super(hash, key, value, next); + } + } + + transient LinkedHashMap.Entry head; + + transient LinkedHashMap.Entry tail; + + //构造函数如下, + public LinkedHashMap(int initialCapacity, + float loadFactor, + boolean accessOrder) { + super(initialCapacity, loadFactor); + this.accessOrder = accessOrder; + } + + } +``` + + +## 用LinkedHashMap实现LRU + + +构造函数中 accessOrder 参数是控制LinkedHashMap 访问顺序,默认为插入顺序(false), true 代表访问顺序。 + +访问顺序的意思是,当有一个entry被访问以后,这个entry就被移动到链表的表尾。 这个特性非常适合 LRU 缓存 (最近最少使用) ; + +插入逻辑,运行自定义删除最老entry的逻辑 + +``` +void afterNodeInsertion(boolean evict) { // possibly remove eldest + LinkedHashMap.Entry first; + if (evict && (first = head) != null && removeEldestEntry(first)) { + K key = first.key; + removeNode(hash(key), key, null, false, true); + } +} +``` + + +重写此方法,维持此映射只保存100个条目的稳定状态,在每次添加新条目时删除最旧的条目。 + +``` +private static final int MAX_ENTRIES = 100; +protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > MAX_ENTRIES; +} +``` + + +基于 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/Hash Table/README.md b/3 Hash Table/README.md similarity index 79% rename from Hash Table/README.md rename to 3 Hash Table/README.md index 0051a7c..0bf1d4f 100644 --- a/Hash Table/README.md +++ b/3 Hash Table/README.md @@ -1,6 +1,4 @@ - - -## 散列表 +# 散列表 本节围绕以下内容展开: @@ -9,17 +7,33 @@ * 冲突处理 * hashmap数据结构 +[Golang 中HashMap实现](HashMap%20in%20Golang.md) +[Java 中HashMap实现](HashMap%20in%20Java.md) +[Java 中LinkedHashMap实现](LinkedHashMap.md) +[Java 中TreeMap实现](TreeMap%20in%20Java.md) + 散列表使用某种算法操作(散列函数)将键转化为数组的索引来访问数组中的数据,这样可以通过Key-value的方式来访问数据,达到常数级别的存取效率。现在的nosql数据库都是采用key-value的方式来访问存储数据。 散列表是算法在时间和空间上做出权衡的经典例子。通过一个散列函数,将键值key映射到记录的访问地址,达到快速查找的目的。如果没有内存限制,我们可以直接将键作为数组的索引,所有的操作操作只需要一次访问内存就可以完成。但这种情况不太现实。 -### 散列函数 + +## Hashmap应用 + +1. cocos2d 游戏引擎 CCScheduler +2. linux 内核bcache。 缓存加速技术,使用SSD固态硬盘作为高速缓存,提高慢速存储设备HDD机械硬盘的性能 +3. hash表在海量数据处理中有广泛应用。如海量日志中,提取出某日访问百度次数最多的IP +4. Java 中HashMap实现。编程语言中HashMap是如何实现的呢? 说说 Java , Golang +5. redis hash结构, set通常也是基于Hash结构实现 + + + +## 散列函数 散列函数就是将键转化为数组索引的过程。且这个函数应该易于计算且能够均与分布所有的键。 -散列函数最常用的方法是`除留余数法`。这时候被除数应该选用`素数`,这样才能保证键值的均匀散步。 +散列函数最常用的方法是`除留余数法`。这时候被除数应该选用`素数`,这样才能保证键值的均匀散布。 散列函数和键的类型有关,每种数据类型都需要相应的散列函数;比如键的类型是整数,那我们可以直接使用`除留余数法`;这里特别说明下,大多数情况下,键的类型都是字符串,这个时候我们任然可以使用`除留余数法`,将字符串当做一个特别大的整数。 @@ -49,7 +63,11 @@ Hash hashCode(char *key){ 当然,还有其他的散列函数,如`平方取中法`, `随机数法`等。 -### 碰撞解决 + +## 碰撞解决 + + +hash 表有一个特性, 随着元素越来越多, 新插入一个元素发生hash冲突的次数就会越来越多, 查找一个元素的速度也会越来越慢。 不同的关键字得到同一个散列地址` f(key1)=f(key2) `,即为碰撞 。这是我们需要尽量避免的情况。常见的处理方法有: @@ -57,7 +75,7 @@ Hash hashCode(char *key){ 2. 线性探测法 -#### 拉链法 +### 拉链法 将大小为M的数组中的每个元素指向一条链表,链表中的每个节点都存储了散列值为该元素索引的键值对。每条链表的平均长度是N/M,N是键值对的总个数。如下图: @@ -78,12 +96,14 @@ Hash hashCode(char *key){ 3. 遍历链表,删除结点 -#### 线性探测法 +### 线性探测法 -使用大小为M的数组保存N个键值对,当碰撞发生时,直接检查散列表中的下一个位置。 +使用大小为M的数组保存N个键值对,当碰撞发生时,直接检查散列表中的下一个位置,如果发现空位置插入新元素。 +查找key时,先通过 hash(key) 得到 index, 看index 处 key 是否已经存在,不存在,就向后遍历数组。 -### 数据结构和算法 + +## 数据结构和算法 这里给出拉链法构造的hashmap的算法,表示如下: @@ -197,11 +217,11 @@ HashMap *_putInList(HashMap *hashMap,int index,Key key,Value value){ ``` -### Hashmap应用 +### 扩容 -1. cocos2d 游戏引擎 CCScheduler -2. linux 内核bcache。 缓存加速技术,使用SSD固态硬盘作为高速缓存,提高慢速存储设备HDD机械硬盘的性能 -3. hash表在海量数据处理中有广泛应用。如海量日志中,提取出某日访问百度次数最多的IP +当hash表保存的键值对数量太多或太少,对hash 表进行扩容和缩容。合理控制内存的使用。 + +redis hash中, rehash发生在扩容或缩容阶段,扩容是发生在元素的个数等于哈希表数组的长度时,进行2倍的扩容;缩容发生在当元素个数为数组长度的10%时,进行缩容 ## 参考 diff --git a/3 Hash Table/TreeMap in Java.md b/3 Hash Table/TreeMap in Java.md new file mode 100644 index 0000000..540abb6 --- /dev/null +++ b/3 Hash Table/TreeMap in Java.md @@ -0,0 +1,107 @@ +# TreeMap in Java + + +java 中 TreeMap的实现原理。 + + + +#### 特点 + +* 有序的Key-value 集合,通过红黑树实现; +* 遍历时元素按照键key的自然顺序进行排序,也可以创建映射时提供的 Comparator 进行排序,具体取决于使用的构造方法 +* TreeMap的基本操作 containsKey、get、put 和 remove 的时间复杂度是 log(n) + + + +#### 引用场景 + +* 一致性Hash算法 + + + +## 使用 + +``` +//创建TreeMap对象: +TreeMap treeMap = new TreeMap(); + + //新增元素: + treeMap.put("hello",1); + treeMap.put("world",2); + treeMap.put("my",3); + treeMap.put("name",4); + + +//遍历元素, 按照key排序 + Set> entrySet = treeMap.entrySet(); + for(Map.Entry entry : entrySet){ + String key = entry.getKey(); + Integer value = entry.getValue(); + System.out.println("TreeMap元素的key:"+key+",value:"+value); + } + +String firstKey = treeMap.firstKey();//获取集合内第一个元素 + +String lastKey =treeMap.lastKey();//获取集合内最后一个元素 + +String lowerKey =treeMap.lowerKey("jiaboyan");//获取集合内的key小于"jiaboyan"的第一个key + +String ceilingKey =treeMap.ceilingKey("jiaboyan");//获取集合内的key大于等于"jiaboyan"的第一个key + +``` + +## 原理 + + +参考 [红黑树](../Tree/9-红黑树\ R-B\ tree/红黑树.md) + + +## 应用 + + + +### 一致性Hash算法 + +一致性Hash算法解决的问题是: 数据均匀的分片存储在不同的机器节点上,且在机器节点上发送增删时 (扩容或者缩容时) ,最少数据集的映射(rehash) 规则发生改变。 + +简单说下原理: + +treeMap 中 key 是 机器节点node 的 hash值, value 是机器节点 IP:port ; 使用TreeMap的 ceilingKey(hash) 这个 API 可以获得 第一个大于 这个 hash值的 节点 + +``` +public class Demo { + + private static String[] servers = {“ip1”, “1p2”, “ip3"}; + + private TreeMap treeMap; // + + /* 一个数据key,会被分片到哪个机器上 */ + public String shardingServer(String key) { + + int dataHash = hash(key); + + //怎么找大于 data_hash 值 的第一个节点? 借助 TreeMap 结构 + String node = getServer(dataHash) + + return node; + } + + /* hash 函数*/ + public int hash(String key){ + + } + + //寻找第一个大于 hash 值的 node + private String getServer(String hash) { + + return treeMap.ceilingKey(hash) + } + +} +``` + + + + + + diff --git a/Hash Table/hash_ref.c b/3 Hash Table/hash_ref.c similarity index 100% rename from Hash Table/hash_ref.c rename to 3 Hash Table/hash_ref.c diff --git a/Hash Table/hashmap.png b/3 Hash Table/hashmap.png similarity index 100% rename from Hash Table/hashmap.png rename to 3 Hash Table/hashmap.png diff --git "a/Binary Tree/1-\344\272\214\345\217\211\346\240\221 /btree.c" "b/4 Tree/1-\344\272\214\345\217\211\346\240\221 /btree.c" similarity index 100% rename from "Binary Tree/1-\344\272\214\345\217\211\346\240\221 /btree.c" rename to "4 Tree/1-\344\272\214\345\217\211\346\240\221 /btree.c" diff --git "a/Binary Tree/1-\344\272\214\345\217\211\346\240\221 /btree/bintree.c" "b/4 Tree/1-\344\272\214\345\217\211\346\240\221 /btree/bintree.c" similarity index 100% rename from "Binary Tree/1-\344\272\214\345\217\211\346\240\221 /btree/bintree.c" rename to "4 Tree/1-\344\272\214\345\217\211\346\240\221 /btree/bintree.c" diff --git "a/Binary Tree/1-\344\272\214\345\217\211\346\240\221 /btree/btree" "b/4 Tree/1-\344\272\214\345\217\211\346\240\221 /btree/btree" similarity index 100% rename from "Binary Tree/1-\344\272\214\345\217\211\346\240\221 /btree/btree" rename to "4 Tree/1-\344\272\214\345\217\211\346\240\221 /btree/btree" diff --git "a/Binary Tree/1-\344\272\214\345\217\211\346\240\221 /btree/public.h" "b/4 Tree/1-\344\272\214\345\217\211\346\240\221 /btree/public.h" similarity index 100% rename from "Binary Tree/1-\344\272\214\345\217\211\346\240\221 /btree/public.h" rename to "4 Tree/1-\344\272\214\345\217\211\346\240\221 /btree/public.h" diff --git "a/Binary Tree/1-\344\272\214\345\217\211\346\240\221 /btree/\351\230\237\345\210\227.h" "b/4 Tree/1-\344\272\214\345\217\211\346\240\221 /btree/\351\230\237\345\210\227.h" similarity index 100% rename from "Binary Tree/1-\344\272\214\345\217\211\346\240\221 /btree/\351\230\237\345\210\227.h" rename to "4 Tree/1-\344\272\214\345\217\211\346\240\221 /btree/\351\230\237\345\210\227.h" diff --git "a/Binary Tree/1-\344\272\214\345\217\211\346\240\221 /rbtree.c" "b/4 Tree/1-\344\272\214\345\217\211\346\240\221 /rbtree.c" similarity index 100% rename from "Binary Tree/1-\344\272\214\345\217\211\346\240\221 /rbtree.c" rename to "4 Tree/1-\344\272\214\345\217\211\346\240\221 /rbtree.c" diff --git "a/Binary Tree/1-\344\272\214\345\217\211\346\240\221 /suffix_tree.c" "b/4 Tree/1-\344\272\214\345\217\211\346\240\221 /suffix_tree.c" similarity index 100% rename from "Binary Tree/1-\344\272\214\345\217\211\346\240\221 /suffix_tree.c" rename to "4 Tree/1-\344\272\214\345\217\211\346\240\221 /suffix_tree.c" diff --git "a/Binary Tree/2-\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/BiSearchTree/README.md" "b/4 Tree/2-\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/BiSearchTree/README.md" similarity index 100% rename from "Binary Tree/2-\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/BiSearchTree/README.md" rename to "4 Tree/2-\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/BiSearchTree/README.md" diff --git "a/Binary Tree/2-\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/BiSearchTree/bisearch" "b/4 Tree/2-\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/BiSearchTree/bisearch" similarity index 100% rename from "Binary Tree/2-\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/BiSearchTree/bisearch" rename to "4 Tree/2-\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/BiSearchTree/bisearch" diff --git "a/Binary Tree/2-\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/BiSearchTree/bisearchtree.c" "b/4 Tree/2-\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/BiSearchTree/bisearchtree.c" similarity index 100% rename from "Binary Tree/2-\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/BiSearchTree/bisearchtree.c" rename to "4 Tree/2-\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/BiSearchTree/bisearchtree.c" diff --git "a/Binary Tree/2-\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/BiSearchTree/bisearchtree.h" "b/4 Tree/2-\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/BiSearchTree/bisearchtree.h" similarity index 100% rename from "Binary Tree/2-\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/BiSearchTree/bisearchtree.h" rename to "4 Tree/2-\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/BiSearchTree/bisearchtree.h" diff --git "a/Binary Tree/2-\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/BiSearchTree/main.c" "b/4 Tree/2-\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/BiSearchTree/main.c" similarity index 100% rename from "Binary Tree/2-\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/BiSearchTree/main.c" rename to "4 Tree/2-\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/BiSearchTree/main.c" diff --git "a/Binary Tree/\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221.md" "b/4 Tree/2-\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221.md" similarity index 89% rename from "Binary Tree/\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221.md" rename to "4 Tree/2-\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221.md" index 6e23115..6e5ce4f 100644 --- "a/Binary Tree/\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221.md" +++ "b/4 Tree/2-\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221/\344\272\214\345\217\211\346\237\245\346\211\276\346\240\221.md" @@ -1,14 +1,19 @@ +# 二叉查找树BST -## 二叉查找树 +二叉查找树(Binary search tree),也叫`有序二叉树(Ordered binary tree)`,`排序二叉树(Sorted binary tree)`。 -二叉查找树(Binary search tree),也叫`有序二叉树(Ordered binary tree)`,`排序二叉树(Sorted binary tree)`。是指一个空树或者具有下列性质的二叉树: +是指一个空树或者具有下列性质的二叉树: 1. 若任意节点的左子树不为空,则左子树上所有的节点值小于它的根节点值 2. 若任意节点的右子树不为空,则右子树上所有节点的值均大于它的根节点的值 3. 任意节点左右子树也为二叉查找树 - 4. 没有键值相等的节点 + 4. 没有键值(key)相等的节点 - ``` + +有序的二叉查找树,中序遍历结果是递增的。 `左小右大` + + +``` typedef int ElemType; typedef struct BiSearchTree{ ElemType key; @@ -18,15 +23,18 @@ BiSearchTree *bisearch_tree_insert(BiSearchTree *tree,ElemType node); int bisearch_tree_delete(BiSearchTree **tree,ElemType node); int bisearch_tree_search(BiSearchTree *tree,ElemType node); - ``` +``` + +### 删除节点 删除节点,需要重建排序树 1) 删除节点是叶子节点(分支为0),结构不破坏 2)删除节点只有一个分支(分支为1),结构也不破坏 - 3)删除节点有2个分支,此时删除节点 + 3)删除节点有2个分支,此时删除节点 ; 需要重建树 + 思路一: 选左子树的最大节点,或右子树最小节点替换 ``` @@ -53,6 +61,7 @@ int bisearch_tree_delete(BiSearchTree **tree,ElemType node){ printf("树为空,或想要删除的节点不存在\n"); return -1; } + //该节点为叶子节点,直接删除 if (!target->rChild && !target->lChild) { @@ -84,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; @@ -107,3 +117,7 @@ int bisearch_tree_delete(BiSearchTree **tree,ElemType node){ } ``` + + + + diff --git "a/Binary Tree/3-\345\271\263\350\241\241\346\240\221AVL/AVLTree.c" "b/4 Tree/3-\345\271\263\350\241\241\346\240\221AVL/AVLTree.c" similarity index 79% rename from "Binary Tree/3-\345\271\263\350\241\241\346\240\221AVL/AVLTree.c" rename to "4 Tree/3-\345\271\263\350\241\241\346\240\221AVL/AVLTree.c" index 2dc947f..fdcb6c3 100644 --- "a/Binary Tree/3-\345\271\263\350\241\241\346\240\221AVL/AVLTree.c" +++ "b/4 Tree/3-\345\271\263\350\241\241\346\240\221AVL/AVLTree.c" @@ -1,36 +1,4 @@ - /* -自平衡二叉查找树(AVL tree): 首先也是二次查找树,其实 任何2个子树的高度差不大于1 -在删除,插入的过程中不断调整子树的高度,保证平均和最坏情况下都是O(logn) - -Adelson-Velskii 和 Landis 1962年 创造。 - -1) 平衡因子 -1 0 1 节点是正常的。平衡因子 = 左子树高度-右字数高度 -2) 除此之外的节点是不平衡的,需要重新平衡这个树。也就是AVL旋转 - -插入节点: - -a: 左旋转(RR型:节点x的右孩子的右孩子上插入新元素)平衡因子由-1 -》-2 时,需要绕节点x左旋转 -b:右旋转(LL型:节点X的左孩子的左孩子上插入新元素) 平衡因子有1-》2,右旋转 -c: 先左后右旋转:(LR型:树中节点X的左孩子的右孩子上插入新元素) 平衡因子从1变成2后,就需要 先绕X的左子节点Y左旋转,接着再绕X右旋转 -d: 先右后左旋转:(RL型:节点X的右孩子的左孩子上插入新元素) - - - 6 6 6 6 - / \ / \ - 5 7 3 9 - / \ \ / - 3 8 5 7 - (LL型) (RR) (LR) (RL) - - -删除节点: - - - - -可以看到,为了保证高度平衡,插入和删除操作代价增加 - 记于2014-2-28 by @nonstriater */ diff --git "a/4 Tree/3-\345\271\263\350\241\241\346\240\221AVL/README.md" "b/4 Tree/3-\345\271\263\350\241\241\346\240\221AVL/README.md" new file mode 100644 index 0000000..1eaf3b9 --- /dev/null +++ "b/4 Tree/3-\345\271\263\350\241\241\346\240\221AVL/README.md" @@ -0,0 +1,56 @@ +# 自平衡二叉查找树(AVL tree) + +自平衡二叉查找树(AVL tree): 首先也是二次查找树,其实 任何2个子树的高度差不大于1 +在删除,插入的过程中不断调整子树的高度,保证查找操作平均和最坏情况下都是O(logn) + +Adelson-Velskii 和 Landis 1962年 创造, 因此叫做 AVL 树。 + +1) 平衡因子 -1 0 1 节点是正常的。平衡因子 = 左子树高度-右字数高度 +2) 除此之外的节点是不平衡的,需要重新平衡这个树。也就是AVL旋转 + + +AVL 实际使用案例 + +* LLVM 的 ImmutableSet,其底层的实现选择为 AVL 树 +* 《一种基于二叉平衡树的P2P覆盖网络的研究》论文 + + + + +## 插入节点 + +a: 左旋转(RR型:节点x的右孩子的右孩子上插入新元素)平衡因子由-1 -》-2 时,需要绕节点x左旋转 +b:右旋转(LL型:节点X的左孩子的左孩子上插入新元素) 平衡因子有1-》2,右旋转 +c: 先左后右旋转:(LR型:树中节点X的左孩子的右孩子上插入新元素) 平衡因子从1变成2后,就需要 先绕X的左子节点Y左旋转,接着再绕X右旋转 +d: 先右后左旋转:(RL型:节点X的右孩子的左孩子上插入新元素) + + + 6 6 6 6 + / \ / \ + 5 7 3 9 + / \ \ / + 3 8 5 7 + (LL型) (RR) (LR) (RL) + + + +## 删除节点 + +可以看到,为了保证高度平衡,插入和删除操作代价增加 + + +## AVL 实现过程中的问题 + +AVL 是严格的平衡二叉树,平衡条件必须满足,即所有节点的左右子树高度差的绝对值不超过1; + +执行插入还是删除操作,只要不满足上面的条件,就要通过旋转来保持平衡,而旋转是非常耗时的,由此我们可以知道AVL树适合用于插入与删除次数比较少,但查找多的情况。 + +由于维护这种高度平衡所付出的代价比从中获得的效率收益还大,故而实际的应用不多,更多的地方是用追求局部平衡而不是非常严格整体平衡的红黑树()。 + + + + + + + + diff --git "a/Binary Tree/\345\255\227\345\205\270\346\240\221.md" "b/4 Tree/4-\345\255\227\345\205\270\346\240\221Trie/README.md" similarity index 92% rename from "Binary Tree/\345\255\227\345\205\270\346\240\221.md" rename to "4 Tree/4-\345\255\227\345\205\270\346\240\221Trie/README.md" index 4526528..6968f78 100644 --- "a/Binary Tree/\345\255\227\345\205\270\346\240\221.md" +++ "b/4 Tree/4-\345\255\227\345\205\270\346\240\221Trie/README.md" @@ -1,9 +1,6 @@ +# 字典树trie -## 字典树trie - -### trie基本 - `字典树`,英文名`Trie树`,Trie一词来自retrieve,发音为/tri:/ “tree”,也有人读为/traɪ/ “try”, 又称`单词查找树` 或 `前缀树`,Trie树,是一种树形结构(多叉树)。 @@ -31,12 +28,66 @@ trie树把每个关键字保存在一条路径上,而不是一个节点中 两个有公共前缀的关键字,在Trie树中前缀部分的路径相同,所以Trie树又叫做前缀树(Prefix Tree)。 -### trie树存储结构和基本操作 -最简单实现 ---- 26个字母表 a-z (没有考虑数字,大小写,其他字符如=-*/) +### trie 优缺点 + +它的优点是: + +1. 插入和查询的效率很高,都是O(m),其中 m 是待插入/查询的字符串的长度 +2. Trie树可以对关键字按字典序排序 +3. 利用字符串的公共前缀来最大限度地减少无谓的字符串比较,提高查询效率 + +缺点: + +1. trie 树比较费内存空间,在处理大数据时会内存吃紧 +2. 当hash函数较好时,Hash查询效率比 trie 更优 + +[知乎这里](http://www.zhihu.com/question/27168319)有个问题:`10万个串找给定的串是否存在`, 对trie和hash两种方案给出了讨论。 + + +[DATrie](https://github.com/kmike/datrie) 是使用python实现的双数组trie树, 双数组可以减少内存的使用量 。有关 double-array trie,可以参考[这篇论文](http://linux.thai.net/~thep/datrie/datrie.html) + + +### trie应用 + +典型应用是:前缀查询,字符串查询,排序 + +* 用于统计,排序和保存大量的字符串(但不仅限于字符串) +* 经常被搜索引擎系统用于文本词频统计 +* 排序大量字符串 +* 用于索引结构 +* 敏感词过滤 + + +### 实际应用问题 + +1. 给你100000个长度不超过10的单词。对于每一个单词,我们要判断他出没出现过,如果出现了,求第一次出现在第几个位置 +分析思路一:trie树 ,找到这个字符串查询操作就可以了,如何知道出现的第一个位置呢?我们可以在trie树中加一个字段来记录当前字符串第一次出现的位置。 + +2. 已知n个由小写字母构成的平均长度为10的单词,判断其中是否存在某个串为另一个串的前缀子串 + +3. 给出N 个单词组成的熟词表,以及一篇全用小写英文书写的文章,请你按最早出现的顺序写出所有不在熟词表中的生词。 +分析:trie树查询单词的应用。先建立N个熟词的前缀树,然后按文章的单词一次查询。 + +4. 给出一个词典,其中的单词为不良单词。单词均为小写字母。再给出一段文本,文本的每一行也由小写字母构成。判断文本中是否含有任何不良单词。例如,若rob是不良单词,那么文本problem含有不良单词。 +分析:先用不良单词建立trie树,然后过滤文本(每个单词都在trie树上查询,查询的复杂度O(1),效率非常高),这正是`敏感词过滤系统(或垃圾评论系统)`的原理。 + +5. 给你N 个互不相同的仅由一个单词构成的英文名,让你将它们按字典序从小到大排序输出 +分析:这是trie树排序的典型应用,建立N个单词的trie树,然后线序遍历整个树,就可以达到效果。 + + + + +## trie树存储结构和基本操作 + +最简单实现 ---- 26个字母表 `a-z` (没有考虑数字,大小写,其他字符如 `=-*/`) + +trie 树存储结构 + +* 用数组存储,浪费空间;如果系统中存在大量字符串,且这些字符串基本没有公共前缀,trie树将消耗大量内存 +* 用链表存储,查询时需要遍历链表,查询效率有所降低 + -子树用数组存储,浪费空间;如果系统中存在大量字符串,且这些字符串基本没有公共前缀,trie树将消耗大量内存 -如果用链表存储,查询时需要遍历链表,查询效率有所降低 ``` define ALPHABET_NUM 26 @@ -100,48 +151,4 @@ int trie_search(trie root, char* key) trie树的增加和删除都比较麻烦,但索引本身就是写少读多,是否考虑添加删除的复杂度上升,依靠具体场景决定。 -### trie 问题 - -它的优点是: - -1. 插入和查询的效率很高,都是O(m),其中 m 是待插入/查询的字符串的长度 -2. Trie树可以对关键字按字典序排序 -3. 利用字符串的公共前缀来最大限度地减少无谓的字符串比较,提高查询效率 - -缺点: - -1. trie 树比较费内存空间,在处理大数据时会内存吃紧 -2. 当hash函数较好时,Hash查询效率比 trie 更优 - -[知乎这里](http://www.zhihu.com/question/27168319)有个问题:`10万个串找给定的串是否存在`, 对trie和hash两种方案给出了讨论。 - - -[DATrie](https://github.com/kmike/datrie) 是使用python实现的双数组trie树, 双数组可以减少内存的使用量 。有关 double-array trie,可以参考[这篇论文](http://linux.thai.net/~thep/datrie/datrie.html) - - -### trie应用 - -典型应用是:前缀查询,字符串查询,排序 - -* 用于统计,排序和保存大量的字符串(但不仅限于字符串) -* 经常被搜索引擎系统用于文本词频统计 -* 排序大量字符串 -* 用于索引结构 -* 敏感词过滤 - -### 实际应用问题 - -1. 给你100000个长度不超过10的单词。对于每一个单词,我们要判断他出没出现过,如果出现了,求第一次出现在第几个位置 -分析思路一:trie树 ,找到这个字符串查询操作就可以了,如何知道出现的第一个位置呢?我们可以在trie树中加一个字段来记录当前字符串第一次出现的位置。 - -2. 已知n个由小写字母构成的平均长度为10的单词,判断其中是否存在某个串为另一个串的前缀子串 - -3. 给出N 个单词组成的熟词表,以及一篇全用小写英文书写的文章,请你按最早出现的顺序写出所有不在熟词表中的生词。 -分析:trie树查询单词的应用。先建立N个熟词的前缀树,然后按文章的单词一次查询。 - -4. 给出一个词典,其中的单词为不良单词。单词均为小写字母。再给出一段文本,文本的每一行也由小写字母构成。判断文本中是否含有任何不良单词。例如,若rob是不良单词,那么文本problem含有不良单词。 -分析:先用不良单词建立trie树,然后过滤文本(每个单词都在trie树上查询,查询的复杂度O(1),效率非常高),这正是`敏感词过滤系统(或垃圾评论系统)`的原理。 - -5. 给你N 个互不相同的仅由一个单词构成的英文名,让你将它们按字典序从小到大排序输出 -分析:这是trie树排序的典型应用,建立N个单词的trie树,然后线序遍历整个树,就可以达到效果。 diff --git "a/Binary Tree/4-\345\255\227\345\205\270\346\240\221Trie/trie.c" "b/4 Tree/4-\345\255\227\345\205\270\346\240\221Trie/trie.c" similarity index 100% rename from "Binary Tree/4-\345\255\227\345\205\270\346\240\221Trie/trie.c" rename to "4 Tree/4-\345\255\227\345\205\270\346\240\221Trie/trie.c" diff --git "a/Binary Tree/\344\274\270\345\261\225\346\240\221.md" "b/4 Tree/5-\344\274\270\345\261\225\346\240\221/\344\274\270\345\261\225\346\240\221.md" similarity index 94% rename from "Binary Tree/\344\274\270\345\261\225\346\240\221.md" rename to "4 Tree/5-\344\274\270\345\261\225\346\240\221/\344\274\270\345\261\225\346\240\221.md" index 1a9e070..eaa5d51 100644 --- "a/Binary Tree/\344\274\270\345\261\225\346\240\221.md" +++ "b/4 Tree/5-\344\274\270\345\261\225\346\240\221/\344\274\270\345\261\225\346\240\221.md" @@ -1,5 +1,4 @@ - -## 伸展树 (splay tree) +# 伸展树 (splay tree) 伸展树是一种自平衡的二叉排序树。为什么需要这些自平衡的二叉排序树? diff --git "a/Binary Tree/\345\220\216\347\274\200\346\240\221.md" "b/4 Tree/6-\345\220\216\347\274\200\346\240\221/\345\220\216\347\274\200\346\240\221.md" similarity index 87% rename from "Binary Tree/\345\220\216\347\274\200\346\240\221.md" rename to "4 Tree/6-\345\220\216\347\274\200\346\240\221/\345\220\216\347\274\200\346\240\221.md" index 47e8368..43c5a72 100644 --- "a/Binary Tree/\345\220\216\347\274\200\346\240\221.md" +++ "b/4 Tree/6-\345\220\216\347\274\200\346\240\221/\345\220\216\347\274\200\346\240\221.md" @@ -1,5 +1,4 @@ - -## 后缀树(suffix tree) +# 后缀树(suffix tree) ###后缀树的应用 diff --git "a/4 Tree/7-B\346\240\221/B+\346\240\221.md" "b/4 Tree/7-B\346\240\221/B+\346\240\221.md" new file mode 100644 index 0000000..cc6df39 --- /dev/null +++ "b/4 Tree/7-B\346\240\221/B+\346\240\221.md" @@ -0,0 +1 @@ +# B+树 diff --git "a/4 Tree/7-B\346\240\221/B\346\240\221.md" "b/4 Tree/7-B\346\240\221/B\346\240\221.md" new file mode 100644 index 0000000..c2b5121 --- /dev/null +++ "b/4 Tree/7-B\346\240\221/B\346\240\221.md" @@ -0,0 +1,79 @@ +# B树 + +一种多路平衡查找树。 + +能保证数据插入和删除情况下,任然保持执行效率。 + +一个M阶的B树满足: + +1. 每个节点最多M个子节点 +2. 除跟节点和叶节点外,其它每个节点至少有M/2个孩子 +3. 根节点至少2个节点 +4. 所有叶节点在同一层,叶节点不包含任何关键字信息 +5. 有k个关键字的页节点包含k+1个孩子 + +也就是说:`根节点到每个叶节点的路径长度都是相同的。` + + + +## 数据结构 + + +``` +typedef struct Item{ + int key; + Data data; +} + +#define m 3 //B树的阶 + + +typedef struct BTNode{ + int degree; //B树的度 + int keynums; //每个节点key的个数 + Item items[m]; + struct BTNode *p[m]; +}BTNode,* BTree; + + +typedef struct{ + BTNode *pt; //指向找到的节点 + int i; // 节点中关键字的序号 (0,m-1) + int tag; //1:查找成功,0:查找失败 +}Result; + + +Status btree_insert(root,target)//插入B树节点 +Result btree_find(root,target)//查找B树节点 +Status btree_delete(root,target)//删除B树节点 +``` + + +## 插入B树节点 + + + + +## 查找B树节点 + + + + +## 删除B树节点 + + + + + + + + + + + + + + + + + diff --git "a/4 Tree/8-\345\240\206/Top-K \351\227\256\351\242\230.md" "b/4 Tree/8-\345\240\206/Top-K \351\227\256\351\242\230.md" new file mode 100644 index 0000000..326ca38 --- /dev/null +++ "b/4 Tree/8-\345\240\206/Top-K \351\227\256\351\242\230.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/Binary Tree/8-\344\272\214\345\217\211\345\240\206(\344\274\230\345\205\210\351\230\237\345\210\227)/heap.c" "b/4 Tree/8-\345\240\206/heap.c" similarity index 100% rename from "Binary Tree/8-\344\272\214\345\217\211\345\240\206(\344\274\230\345\205\210\351\230\237\345\210\227)/heap.c" rename to "4 Tree/8-\345\240\206/heap.c" diff --git "a/4 Tree/8-\345\240\206/pq-1.jpg" "b/4 Tree/8-\345\240\206/pq-1.jpg" new file mode 100644 index 0000000..e8b9ceb Binary files /dev/null and "b/4 Tree/8-\345\240\206/pq-1.jpg" differ diff --git "a/4 Tree/8-\345\240\206/pq-1.png" "b/4 Tree/8-\345\240\206/pq-1.png" new file mode 100644 index 0000000..78202ff Binary files /dev/null and "b/4 Tree/8-\345\240\206/pq-1.png" differ diff --git "a/4 Tree/8-\345\240\206/\345\240\206.md" "b/4 Tree/8-\345\240\206/\345\240\206.md" new file mode 100644 index 0000000..c6e8d6e --- /dev/null +++ "b/4 Tree/8-\345\240\206/\345\240\206.md" @@ -0,0 +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-\347\272\242\351\273\221\346\240\221 R-B tree/\347\272\242\351\273\221\346\240\221.md" "b/4 Tree/9-\347\272\242\351\273\221\346\240\221 R-B tree/\347\272\242\351\273\221\346\240\221.md" new file mode 100644 index 0000000..48aa007 --- /dev/null +++ "b/4 Tree/9-\347\272\242\351\273\221\346\240\221 R-B tree/\347\272\242\351\273\221\346\240\221.md" @@ -0,0 +1,58 @@ +# 红黑树 (red-black tree) + +红黑树(Red Black Tree) 是一种自平衡二叉查找树。一种特化的[AVL树](../3-平衡树AVL/README.md),在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。 它可以在O(log n)时间内做查找,插入和删除。 + +它的每个结点都被“着色”为红色或者黑色,这些结点的颜色被用来检测树的平衡性。 + + +### 应用场景 + +* C++ STL的map和set +* java 中 HashMap、TreeMap 的底层实现,当HashMap中元素大于8个时,HashMap底层存储实现改为红黑树,以提高元素搜索速度。 +关于 HashMap 实现解析参考 [这里](../../3%20HashTable/HashMap%20in%20Java.md) +* 广泛应用Linux 的进程管理、内存管理,设备驱动及虚拟内存跟踪 +* epoll的的的实现采用红黑树组织管理的的的sockfd,以支持快速的增删改查 +* Nginx的的的中用红黑树管理定时器,因为红黑树是有序的,可以很快的得到距离当前最小的定时器 + + +### RB tree 特点 + +* 每个节点非红即黑 +* 根节点是黑的 +* 每个叶节点(叶节点即树尾端NULL指针或NULL节点)都是黑的 +* 如果一个节点是红的,那么它的两儿子都是黑的 +* 对于任意节点而言,其到叶子点树NULL指针的每条路径都包含相同数目的黑节点 +* 每条路径都包含相同的黑节点 + + +在每个节点增加一个存储位表示节点的颜色,可以是红或黑(非红即黑);通过对任何一条从根到叶子的路径上各个节点着色的方式的限制,红黑树确保没有一条路径会比其它路径长出两倍; + +因此,红黑树是一种弱平衡二叉树(由于是弱平衡,可以看到,在相同的节点情况下,AVL树的高度低于红黑树)。相对于要求严格的[AVL树](../3-平衡树AVL/README.md)来说,它的旋转次数少,所以对于搜索,插入,删除操作较多的情况下,我们就用红黑树。 + + +也就是说,红黑树牺牲掉一定的平衡性(牺牲查找性能),换来了 插入,删除操作时 更少的旋转次数带来的开销。 + + +### 红黑树 & B+ 树对比 + +* 红黑树多用在内部排序,即全放在内存中的 +* B+树多用于外存上时,B+也被成为一个磁盘友好的数据结构; 这也是为什么 mysql索引使用b+树而不使用红黑树 + + +为什么使用 红黑树 而不是 B+ 树呢?原因如下: + +* 没有范围查找, 不需要 B+ +* 不需要多路平衡树,使用二路平衡,实现简单,且红黑树能兼顾 查找,删除操作的性能 + + + + + + + + + + + + + diff --git "a/4 Tree/92-\345\271\266\346\237\245\351\233\206/\345\271\266\346\237\245\351\233\206.md" "b/4 Tree/92-\345\271\266\346\237\245\351\233\206/\345\271\266\346\237\245\351\233\206.md" new file mode 100644 index 0000000..4b9a111 --- /dev/null +++ "b/4 Tree/92-\345\271\266\346\237\245\351\233\206/\345\271\266\346\237\245\351\233\206.md" @@ -0,0 +1 @@ +# 并查集 \ No newline at end of file diff --git a/4 Tree/README.md b/4 Tree/README.md new file mode 100644 index 0000000..6b16a06 --- /dev/null +++ b/4 Tree/README.md @@ -0,0 +1,66 @@ +# 树🌲 + +介绍树相关的算法 + +* 二叉树 +* 二叉查找树 +* AVL树 +* 红黑树 +* B树 : B树, B+树(mysql索引使用B+树的数据结构) +* 字典树trie(前缀树,单词查找树) +* 伸展树 +* 后缀树 +* 红黑树 +* 二叉堆(优先队列) +* Treap 树 +* 赫夫曼编码 Huffman + + +## 二叉树 + + +[快速排序](../6%20Sort/README.md)就是个二叉树的前序遍历,归并排序就是个二叉树的后序遍历 + + +## [二叉查找树BST](2-二叉查找树/二叉查找树.md) + +有序的二叉树,中序遍历结果是递增的 + +## [AVL树](3-平衡树AVL/README.md) + +绝对的平衡二叉树; + + +## [红黑树](9-红黑树%20R-B%20tree/红黑树.md) + +弱平衡二叉树;使用广泛 + + +## [字典树trie](4-字典树Trie/README.md) + +字典树也叫前缀树,单词查找树 + + +## [伸展树](5-伸展树/伸展树.md) + + +## [后缀树](6-后缀树/后缀树.md) + +## B树 + +* [B树](7-B树/B树.md) +* [B+树](7-B树/B+树.md) mysql 索引使用 B+树 的数据结构 + + +## [二叉堆](8-堆/堆.md) + + + + + + + + + + + diff --git "a/Binary Tree/\350\265\253\345\244\253\346\233\274\347\274\226\347\240\201.md" "b/4 Tree/huffman tree/\350\265\253\345\244\253\346\233\274\347\274\226\347\240\201.md" similarity index 79% rename from "Binary Tree/\350\265\253\345\244\253\346\233\274\347\274\226\347\240\201.md" rename to "4 Tree/huffman tree/\350\265\253\345\244\253\346\233\274\347\274\226\347\240\201.md" index b121e98..c35c115 100644 --- "a/Binary Tree/\350\265\253\345\244\253\346\233\274\347\274\226\347\240\201.md" +++ "b/4 Tree/huffman tree/\350\265\253\345\244\253\346\233\274\347\274\226\347\240\201.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,8 +47,12 @@ struct node{ 被编码的文本长度 unsigned int size 字符频率表 unsigned char freqs[NUM_CHARS] + ###解压缩 1. 读取文件头 2. 遍历编码后的bits,从赫夫曼树的根节点出发,遇到0,进入左子树,遇到1进入右子树,直到叶节点 + + + diff --git "a/5 Graph/DFS \345\222\214 BFS.md" "b/5 Graph/DFS \345\222\214 BFS.md" new file mode 100644 index 0000000..ed7d73c --- /dev/null +++ "b/5 Graph/DFS \345\222\214 BFS.md" @@ -0,0 +1,17 @@ +# DFS 和 BFS 搜索算法 + +DFS: 深度优先搜索,以深度为准则,先一条路走到底,直到达到目标; 没有达到目标又无路可走了,那么则退回到上一步的状态,走其他路。这便是回溯上来。 + +BFS:广度优先搜素,在面临一个路口时,把所有的岔路口都记下来,然后选择其中一个进入,然后将它的分路情况记录下来,然后再返回来进入另外一个岔路,并重复这样的操作。 + + + +> DFS用递归的形式,用到了栈结构,先进后出; BFS选取状态用队列的形式,先进先出。 + + + + + + + + diff --git a/5 Graph/README.md b/5 Graph/README.md new file mode 100644 index 0000000..0774d79 --- /dev/null +++ b/5 Graph/README.md @@ -0,0 +1,79 @@ +# 图 + +### 什么是图 + +图论(Graph theory)是数学的一个分支,它以图为研究对象,研究顶点和边组成的图形的数学理论和方法。图论中的图是由若干给定的顶点及连接两顶点的边所构成的图形,这种图形通常用来描述某些事物之间的某种特定关系,用顶点代表事物,用连接两顶点的边表示相应两个事物间具有这种关系。 + +图论的研究对象相当于一维的拓扑学。 + + +### 应用场景 + +工业界有哪些应用场景? + +* 匹配,如打车中司乘匹配引擎,如何做到效率最优 +* 并行任务调度: 一组任务,任务有优先级,如何合理安排任务调度,在最短时间内完成 +* 导航路径规划:在使用导航软件时,用户在选择一个开始地点和目的地之后导航软件会给出各种如路程最短,不走高速,时长最短等方案 +* 社区发现: 在好友关系中,根据社区之间联系或紧密,利用图 louvain 算法或者其他算法对用户进行分群从而达到精准营销,个性化服务等 +* 金融贷后催收:利用图算法找出符合条件的失联人的联系人,从而提高催收失联修复的覆盖率、有效联系率,助力不良资产的回收 +* 套汇 + + +### 基本概念 + +** 有向图 ** + +有向图,就是有方向的图 + +** 无向图 ** + +就是没有方向的图 + +** 环 ** + +首尾相接的路径我们就把它叫做一个环。 + + +# 图的存储结构 + +* 对象和指针 +* 邻接矩阵 (二维数组) +* 邻接表 + + +图数据结构表示: + +``` + +``` + + +## 图的操作 + +#### 遍历 + +* 广度优先 BFS +* 深度优先 DFS + +Dijkstra +A* + +用于游戏编程和分布式计算 + + +## 图延伸 + +* 二部图 +* 有向无环图 DAG + + +### 参考 + +https://www.jiqizhixin.com/articles/2019-05-16-14 + + + + + + + diff --git "a/5 Graph/\344\272\214\345\210\206\345\233\276/\344\272\214\351\203\250\345\233\276.md" "b/5 Graph/\344\272\214\345\210\206\345\233\276/\344\272\214\351\203\250\345\233\276.md" new file mode 100644 index 0000000..e021fe3 --- /dev/null +++ "b/5 Graph/\344\272\214\345\210\206\345\233\276/\344\272\214\351\203\250\345\233\276.md" @@ -0,0 +1 @@ +# 二部图 \ No newline at end of file diff --git "a/5 Graph/\346\213\223\346\211\221\346\216\222\345\272\217.md" "b/5 Graph/\346\213\223\346\211\221\346\216\222\345\272\217.md" new file mode 100644 index 0000000..9ebb39d --- /dev/null +++ "b/5 Graph/\346\213\223\346\211\221\346\216\222\345\272\217.md" @@ -0,0 +1,4 @@ +# 拓扑排序 + + + diff --git "a/5 Graph/\346\234\200\345\260\217\347\224\237\346\210\220\346\240\221.md" "b/5 Graph/\346\234\200\345\260\217\347\224\237\346\210\220\346\240\221.md" new file mode 100644 index 0000000..5570c30 --- /dev/null +++ "b/5 Graph/\346\234\200\345\260\217\347\224\237\346\210\220\346\240\221.md" @@ -0,0 +1,4 @@ +# 最小生成树 + + + diff --git "a/5 Graph/\346\234\200\347\237\255\350\267\257\345\276\204.md" "b/5 Graph/\346\234\200\347\237\255\350\267\257\345\276\204.md" new file mode 100644 index 0000000..d933843 --- /dev/null +++ "b/5 Graph/\346\234\200\347\237\255\350\267\257\345\276\204.md" @@ -0,0 +1,31 @@ +# 最短路径算法 + + +最短路径问题: 寻找图(由结点和路径组成的)中两结点之间的最短路径 + + +实现算法 + +* A* 算法 +* Floyd +* Dijkstra(迪杰斯特拉) +* bellman-ford +* spfa + + +## A* 算法 + +`A*(A-Star)`算法是一种静态路网中求解最短路径最有效的直接搜索方法; 算法中的距离估算值与实际值越接近,最终搜索速度越快。 + + + +## Dijkstra:最短路径算法 + +Dijkstra 解决图中一点到其余各点到最短路径的问题 + +Dijkstra是荷兰的计算机科学家,提出”信号量和PV原语“,"解决哲学家就餐问题",”死锁“也是它提出来的) + + + + + diff --git a/Sort/8.c b/6 Sort/8.c similarity index 100% rename from Sort/8.c rename to 6 Sort/8.c diff --git a/Sort/README.md b/6 Sort/README.md similarity index 56% rename from Sort/README.md rename to 6 Sort/README.md index 6ea2034..fd0ff97 100644 --- a/Sort/README.md +++ b/6 Sort/README.md @@ -1,26 +1,35 @@ +# 排序算法 +**排序的稳定性** 是指对于相等的元素,排序之后,任然保存2个元素的位置没有变,就是稳定的排序,反之就是不稳定排序。 -## 排序算法 -**排序的稳定性** 是指对于相等的元素,排序之后,任然保存2个元素的位置没有变,就是稳定的排序,反之就是不稳定排序。 +* 交换排序算法 +* 桶排序 +### 交换排序算法 -**交换排序算法** +* 冒泡排序 :没轮确定一个最大的数排后面 +* 选择排序 :每轮选择最小的数排前面 +* 快排 : 选最后一个作为pivot(基数), 将数据分为 左边小于 pivot, 右边大于 pivot; 分治递归, 二叉树的前序遍历思路 +* 归并排序 : 将无序的数组,分成2个子数组分别排序,然后再merge ; 分治递归, 二叉树的后续遍历思路 +* 插入排序 : +* 希尔排序 : +* [堆排序](../4%20Tree/8-堆/堆.md) : -* 冒泡排序 -* 插入排序 -* 选择排序 -* 希尔排序 -* 快排 -* 归并排序 -* 堆排序 -**线性排序算法** +### 线性排序算法 * 桶排序 -## 交换排序算法 +### 小结 + +常见的排序算法都是比较排序,比较排序的时间复杂度通常为 O(n^2) 或 O(nlogn) +但是如果带排序的数字有一些特俗性时,我们可以根据这来设计更加优化的排序算法。 + + + +# 交换排序算法 排序算法的复杂度由 `比较的次数` 和 `交换的次数` 一起决定。 @@ -33,6 +42,8 @@ 在a【i,n】中最小的元素和 a[i]交换位置。空间复杂度O(1),时间复杂度 O(n^2) +![](./selectsort.gif) + ### 冒泡排序 @@ -42,55 +53,51 @@ 这样,即使是排好序的拿冒泡排序排序,比较的时间复杂度O(n^2) +![](./bubblesort.gif) +### 快速排序 -### 插入排序 - -1. 第一个元素算作已经排好 -2. 取下一个元素,从已经排好的序列元素中,从后向前扫描 -3. 如果排好序的元素大于 新元素,排好序的元素移到下一个位置 -4. 重复3,直到直到最后的插入位置 -5. 重复2 - -类似插入扑克牌的效果 - -最坏的情况: 待排序的是一个逆序排放的数组,这样导致每一轮都要移动元素;此时复杂度是是0(n^2) -最好的情况: 待排序的是一已经顺序排放的数字,此时只需要做一轮比较就够了 0(n)。因此可以看到,**对大部分数据已经有序这样的数组排序,使用`插入排序`非常有优势** - -空间复杂度O(1) - - -### 希尔排序 -递减增量排序算法,对`插入排序`的改进,实质是分组插入排序,又叫`缩小增量排序` +时间复杂度 平均复杂度 O(n * logn) , 最坏 O(n^2) +空间复杂度 +快速排序是对冒泡排序的改进,划分交换排序。 -1. 先将待排数列分割成若干子序列(增量为m) -2. 对每个子序列使用`插入排序` -3. 减小增量,再排序 -4. 对全体元素做一次`插入排序` -`希尔排序`提升排序的奥秘就在于`数据元素越有序,使用插入排序效率越高` +递归一次,pivot 左边都比它小,右边都比它大。这是递归,分治的思想。 +> 快速排序就是个二叉树的前序遍历思路,归并排序就是个二叉树的后序遍历思路 -### 快速排序 +代码框架如下 +```C +void quicksort(int *a, int left, int right){ + if (left=tmp){ + while(i=tmp){ j--; } if (i1) - { - merge_sort(a,length/2); - merge_sort(a+length/2,length-length/2); - merge_array(a,length/2,a+length/2,length-length/2); + +归并排序的代码框架,套用二叉树的后续遍历思路,如下: + +``` +void sort(int[] nums, int low, int high) { + int mid = (low + high) / 2; + sort(nums, low, mid); + sort(nums, mid + 1, high); + + /****** 后序遍历位置 ******/ + // 合并两个排好序的子数组 + merge(nums, low, mid, high); + /************************/ +} + + +//合并连个有序数组; 从后往前merge +public void merge(int[] arr,int low,int mid,int high,int[] tmp){ + int i = 0; + int j = low,k = mid+1; //左边序列和右边序列起始索引 + + while(j <= mid && k <= high){ + if(arr[j] < arr[k]){ + tmp[i++] = arr[j++]; + }else{ + tmp[i++] = arr[k++]; + } + } + + //若左边序列还有剩余,则将其全部拷贝进tmp[]中 + while(j <= mid){ + tmp[i++] = arr[j++]; + } + + while(k <= high){ + tmp[i++] = arr[k++]; + } + + for(int t=0;t=0 && a[j] > tmp ; j--) + { + a[j+1]=a[j]; + } + // j+1是插入的位置 + a[j+1]=tmp; + + } + +} + + +// 选择排序 + +void select_sort(int *a,int length){ + + int min_index,tmp; + int j; + + for (int i = 0; i < length; ++i) + { + for (j = i+1 ,min_index=i; j < length; ++j)// 不能写成for (int j = i+1 ,min_index=i; j < length; ++j) + { + if (a[min_index]>a[j]) + { + min_index=j; + } + } + + //min_index是最小的元素的index + if (min_index!=i) + { + tmp=a[i]; + a[i]=a[min_index]; + a[min_index]=tmp; + + } + + } + +} + + +// 冒泡排序 + +void bubble_sort(int *a, int length){ + + int tmp ; + + for (int i = 0; i < length-1; ++i) // 第i轮排序 + { + for (int j = 0; j < length-i; ++j) + { + if (a[j] > a[j+1]) + { + tmp = a[j]; + a[j] = a[j+1]; + a[j+1] = tmp; + } + } + + } + +} + + +// 快速排序 + +// 挖坑填数,2边向中间扫描 +int partion(int *a, int start,int end){ + + int i=start,j=end; + int tmp = a[i]; // 这里要做越界检查 + + while(i=tmp){ + j--; + } + if (i= a[max])// parent > parent >= max(left, right) + { + break; + } + else + { + heap_swop(&a[parent],&a[max]); + parent = max; //继续向下, 比对, 交换, 保证所有树 父节点 >= 子节点 + max = 2 * parent + 1; + } + + } + +} + + +// 从第一个非叶子节点a[(length-2)/2],开始做调整, 跟自己的子节点比较,把最大的孩子换上来就是创建最大堆, +//反之,把最小的孩子换上来就是创建最小堆 一直到a[0] +void heap_build(int *a,int length){ + + + for (int i = (length-2)/2; i >=0 ; --i) + { + // 三个数里取最大的一个 a[i],a[2i+1],a[2i+2],跟a[i]交换;然后是 a[(i-1)/2],a[i],a[i+1] .. 一直到a[0] + heap_public_adjust(a,i,length); + + } + +} + +// 自顶向下调整 +void heap_adjust(int *a,int length){ + + heap_public_adjust(a,0,length); //对0号调整 +} + + +void heap_sort(int *a, int length){ + + // 建立堆 大根堆,递增排序 + heap_build(a,length); + + for (int i = length-1; i >0; --i) + { + //交换 + heap_swop(&a[0],&a[i]); + //调整 + heap_adjust(a,i); + } + +} + + + +// 归并排序 + +// 合并2个有序数组,分配一个临时空间,装a,b的结果,最后,将合并结果拷贝到数组A,是否临时空间 +void merge_array(int *a,int size_a,int *b, int size_b){ + + + int *tmp = malloc( (size_a+size_b)*sizeof(int) ); + int i,j,k; + i=j=k=0; + + while(ib[j])?b[j++]:a[i++]; + } + + while(i1) + { + + merge_sort(a,length/2); + merge_sort(a+length/2,length-length/2); + + merge_array(a,length/2,a+length/2,length-length/2); + } + + + +} + + +/////////////////////////////////////////////// + + +#define Max_Number 5000 + +int main(){ + + //int a[] = {4,87,2,32,5,2,9,49,49,23,45,2,41}; + //准备5000个数 + int a[Max_Number]; + for (int i = 0; i < Max_Number; ++i) + { + a[i]=rand()%Max_Number; + } + + clock_t start,finish; + + + + start = clock(); + //merge_sort(a,sizeof(a)/sizeof(int)); // 0.002s,可以看到,归并排序还是很快的 + heap_sort(a,sizeof(a)/sizeof(int)); // + //quicksort(a,0,sizeof(a)/sizeof(int)-1); // 0.01s + //insert_sort(a,sizeof(a)/sizeof(int)); // 3.85s + //select_sort(a,sizeof(a)/sizeof(int)); // 5.3s + //bubble_sort(a,sizeof(a)/sizeof(int)); // 12.5s + finish = clock(); + + printf("after sort:\n"); + for (int i = 0; i < sizeof(a)/sizeof(int); ++i) + { + printf(" %d ",a[i]); + } + printf("time eclipse: %.6f sec\n", (double)(finish-start)/CLOCKS_PER_SEC); // CLOCKS_PER_SEC 1000 clock()是毫秒 + + return 0; +} + + diff --git a/Sort/mergesort.gif b/6 Sort/mergesort.gif similarity index 100% rename from Sort/mergesort.gif rename to 6 Sort/mergesort.gif diff --git a/Sort/qsort.gif b/6 Sort/qsort.gif similarity index 100% rename from Sort/qsort.gif rename to 6 Sort/qsort.gif diff --git a/Sort/selectsort.gif b/6 Sort/selectsort.gif similarity index 100% rename from Sort/selectsort.gif rename to 6 Sort/selectsort.gif diff --git a/Sort/shellsort.gif b/6 Sort/shellsort.gif similarity index 100% rename from Sort/shellsort.gif rename to 6 Sort/shellsort.gif diff --git "a/6 Sort/\345\244\226\346\216\222\345\272\217.md" "b/6 Sort/\345\244\226\346\216\222\345\272\217.md" new file mode 100644 index 0000000..6c426c6 --- /dev/null +++ "b/6 Sort/\345\244\226\346\216\222\345\272\217.md" @@ -0,0 +1,5 @@ +# 外排序 + + + + diff --git a/7 Search/README.md b/7 Search/README.md new file mode 100644 index 0000000..20c665e --- /dev/null +++ b/7 Search/README.md @@ -0,0 +1,104 @@ +# 查找算法 + +* 顺序查找 +* 二分查找 +* 分块查找 +* 动态查找 +* 哈希表 + + +## 顺序查找 + +顺序表查找。复杂度O(n) + +## 二分查找 + +有序表中查找我们可以使用二分查找。 + +``` +/* +eg: [1,3,5,6,7,9] k=6 +@return 返回元素的索引下表,找不到就返回-1 +*/ +int binary_search(int *a,int length,int k){ + int low = 0; + int high = length-1; + int mid; + + while(low k) high = mid-1; + } + + return -1; +} +``` + + +``` +注意细节 +mid+1/mid-1 , 否则的话,有可能死循环 + +while(low <= high) 而不是 while(low target) { + right = mid; // 注意 , 这里没有 -1 + } + } + return left; +} +``` + + + + +## 分块查找 + +块内无序,块之间有序;可以先二分查找定位到块,然后再到块中顺序查找。 + + +## 动态查找 + +这里之所以叫 动态查找表,是因为表结构是查找的过程中动态生成的。查找结构通常是二叉排序树,AVL树,B- ,B+等。这部分的内容可以去看『二叉树』章节 + + +## 哈希表 + +哈希表以复杂度O(1)的成绩位列所有查找算法之首,大量查找的数据结构中都可以看到哈希表的应用。 + + + + + + + + + + + diff --git a/8 Algorithms Analysis/README.md b/8 Algorithms Analysis/README.md new file mode 100644 index 0000000..007526c --- /dev/null +++ b/8 Algorithms Analysis/README.md @@ -0,0 +1,28 @@ +# 算法分析思路 + +详细介绍每一种算法设计的思路,并为每种方法给出一个经典案例的详细解读,总结对应设计思路,最后给出其它案例,以供参考。 + + +* [递归](递归.md) +* [分治算法](分治算法.md) +* [动态规划](动态规划.md) +* [回溯法](回溯法.md) +* [迭代法](迭代法.md) +* [穷举搜索法](穷举搜索法.md) +* [贪心算法](贪心算法.md) + + + + +### 总结 + +`贪心法`、`分治法`、`动态规划` 都是将问题归纳为根小的、相似的子问题,通过求解子问题产生全局最优解。 + + + +## 参考 + +《算法设计与分析基础》 Anany Levitin + + + diff --git "a/8 Algorithms Analysis/\345\210\206\346\262\273\347\256\227\346\263\225.md" "b/8 Algorithms Analysis/\345\210\206\346\262\273\347\256\227\346\263\225.md" new file mode 100644 index 0000000..be1eb73 --- /dev/null +++ "b/8 Algorithms Analysis/\345\210\206\346\262\273\347\256\227\346\263\225.md" @@ -0,0 +1,30 @@ +# 分治算法 + + +将一个难以直接解决的大问题,分割成一些规模较小的相同问题,各个击破,分而治之。 + +分治算法常用[递归](./递归.md) 实现 + +1)问题缩小的小规模可以很容易解决 +2)问题可以分解为规模较小相同问题 +3)子问题的解可以合并为该问题的解 +4)各个子问题相互独立,(如果这条不满足,转为`动态规划`求解) + +分治法的步骤: + +1. 分解 +2. 解决 +3. 合并 + + +#### 大整数乘法 + +如 `26542123532213598*345987342245553677884` + + + +#### 其它案例 + +* 快速排序 +* [归并排序](../6%20Sort/README.md) +* 最大子数组和 diff --git "a/8 Algorithms Analysis/\345\212\250\346\200\201\350\247\204\345\210\222.md" "b/8 Algorithms Analysis/\345\212\250\346\200\201\350\247\204\345\210\222.md" new file mode 100644 index 0000000..d9d708e --- /dev/null +++ "b/8 Algorithms Analysis/\345\212\250\346\200\201\350\247\204\345\210\222.md" @@ -0,0 +1,282 @@ +# 动态规划DP + +动态规划(英语:Dynamic programming,简称 DP),通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。复杂问题不能分解成几个子问题,而分解成一系列子问题 ; + +DP通常基于一个递推公式及一个(或多个)初始状态,当前子问题解由上一次子问题解推出。 + + +动态规划算法的关键在于解决冗余,以空间换时间的技术,需要存储过程中的各种状态。可以看着是`分治算法`+`解决冗余` + + +动态规划算法也可以说是 `记住求过的解来节省时间` ; 比如 Fibonacci数列 中,先直接从最小,最简单的 f(1) , f(2) 开始,自低向上一直到 f(20) , 这就是动态规划的思路 + +【初始状态】→【决策1】→【决策2】→…→【决策n】→【结束状态】 + + +### DP 应用场景 + +如果一个问题,可以把所有可能的答案穷举出来,并且穷举出来后,发现存在重叠子问题,就可以考虑使用动态规划。 + +使用动态规划算法的问题的特征是`子问题的重叠性`,`最优子结构` ,否则动态规划算法不具备优势。 + +动态规划的核心思想就是穷举求最值; 动态规划问题的一般形式就是`求最值`,动态规划其实是运筹学的一种最优化方法,只不过在计算机问题上应用比较多,比如说: + +* Fibonacci数列 [代码参考这里 递归](./递归.md) +* 最大子数组和 +* 凑零钱问题 +* 股票问题 [代码参考这里](https://github.com/nonstriater/deep-in-java/blob/master/src/main/java/com/nonstriater/deepinjava/algo/list/stock/BestChance.java) +* 打家劫舍问题 : num[i] 代表第i个房子中的现金数目,从房子中取钱的最大数目,约束是相邻房子的钱不能同时取出 +* 接雨水问题 :num[i]表示柱子高度,计算下雨之后能接多少雨水 +* 青蛙跳阶问题: 一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 10 级的台阶总共有多少种跳法。 +* 最小编辑距离 +* 最长递增子序列 (LIS Longest Increasing Subsequence),如`【5,6,7,3,2,8】` 最长子序列 `【5,6,7,8】`, 输出4 + +* 最长公共子序列 (LIS Longest public Subsequence) +* 最长回文子序列 (LIS Longest public Subsequence) +* 0-1 背包问题 + + +[更多动态规划案例代码实现参考deep-in-java])(https://github.com/nonstriater/deep-in-java/tree/master/src/main/java/com/nonstriater/deepinjava/algo/framework/dynamic) + +青蛙跳阶问题 + +``` +想跳到第10级台阶,要么是先跳到第9级,然后再跳1级台阶上去;要么是先跳到第8级,然后一次迈2级台阶上去。 +同理,要想跳到第9级台阶,要么是先跳到第8级,然后再跳1级台阶上去;要么是先跳到第7级,然后一次迈2级台阶上去。 +要想跳到第8级台阶,要么是先跳到第7级,然后再跳1级台阶上去;要么是先跳到第6级,然后一次迈2级台阶上去 + +即通用公式为: f(n) = f(n-1) + f(n-2) + +那f(2) 或者 f(1) 等于多少呢? + +当只有2级台阶时,有两种跳法,第一种是直接跳两级,第二种是先跳一级,然后再跳一级。即f(2) = 2; +当只有1级台阶时,只有一种跳法,即f(1)= 1; + +``` + + + + +### DP VS 分治法 + +与[分治法](分治算法.md)不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。 +若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。我们可以用一个表来记录所有已解的子问题的答案。 + + +### DP VS 回溯法 + +DP 和 回溯法 都会用到递归 + +动态规划的暴力求解阶段就是回溯算法。只是有的问题具有重叠子问题性质,可以用 dp table 或者备忘录优化,将递归树大幅剪枝,这就变成了动态规划;而有些问题没有重叠子问题,也就是[回溯算法](回溯法.md)问题了,复杂度非常高是不可避免的 + + +## DP 解题模板 + + +基本步骤 + +* 划分问题 +* 状态定义, 穷举「状态」, bad case +* 状态转移方程, 这一步最为困难 ; 暴力解法就是状态转移方程 +* 状态压缩 + + + +``` +# 初始化 base case +dp[0][0][...] = base + +# 进行状态转移 +for 状态1 in 状态1的所有取值: + for 状态2 in 状态2的所有取值: + for ... + dp[状态1][状态2][...] = 求最值(选择1,选择2...) +``` + + + +## 最大子数组和 + +示例: 输入:nums = [-2,1,-3,4,-1,2,1,-5,4],连续子数组 [4,-1,2,1] 的和最大, 输出:6 + + +dp[i] 表示 nums[i] 为结尾的「最大子数组和」; dp[n-1] 就是 nums 的「最大子数组和」 +状态转移 : `dp[i] = Math.max(nums[i], nums[i] + dp[i - 1]);` +状态压缩:注意到 dp[i] 仅仅和 dp[i-1] 的状态有关 + + +```Java +public static int largestSubSequenceSum2(int[] nums){ + + int n = nums.length; + if (n == 0) return 0; + + // base case + int dp_0 = nums[0]; + int dp_1 = 0; + int res = dp_0; + + for (int i = 1; i < n; i++) { + // dp[i] = max(nums[i], nums[i] + dp[i-1]) + dp_1 = Math.max(nums[i], nums[i] + dp_0); + dp_0 = dp_1; + + // 顺便计算最大的结果, 保存到 res + res = Math.max(res, dp_1); + } + + return res; + + } +``` + + +## 凑零钱问题 + +凑零钱问题[视频解读参考这里](https://www.ixigua.com/6881883015832666635?wid_try=1) +[实现代码这里](https://github.com/nonstriater/deep-in-java/tree/master/src/main/java/com/nonstriater/deepinjava/algo/framework/dynamic) + +如果使用贪心策略,并不能得到最优解。 + +比如:给定一个面值list : 1,2,4,5,7,10; 给定一个 target : 14, 求 凑齐 target=14 , 最少的零钱数量 + +思路: + +如想求 amount = 14 时的最少硬币数, + 如果你知道凑出 amount = 13 的最少硬币数(子问题), 再加 1 个 1元面值 即可 + 如果你知道凑出 amount = 12 的最少硬币数(子问题), 再加 1 个 2元面值 即可 + 如果你知道凑出 amount = 10 的最少硬币数(子问题), 再加 1 个 4元面值 即可 + 如果你知道凑出 amount = 9 的最少硬币数(子问题), 再加 1 个 5元面值 即可 + + +```Java +for (int coin : coins) { + // 计算子问题的结果 + int subProblem = dp(coins, amount - coin); + // 子问题无解则跳过 + if (subProblem == -1) continue; + + // 在子问题中选择最优解,然后加一 + res = Math.min(res, subProblem + 1); + } +``` + +通过备忘录消除子问题(不用递归了), dp 数组的定义:当目标金额为 i 时,至少需要 dp[i] 枚硬币凑出 +如想求 amount = 14 时的最少硬币数, dp[14] + dp[0] = 0 + dp[1] = 1 + dp[2] = 1个2元的,dp[1] + 1个1元 + ... + dp[9] = dp[8] + 1个1元, dp[7] + 1个2元, dp[5] + 1个4元, dp[4] + 1个5元,dp[2] + 1个7元 + 1 + dp[i-coin] 从这些可选项里选择最小的 + + +```Java +//对于 dp[i], 遍历可选项, 选择最小的 +for(int coin : coins) { + if (i < coin) { + continue; + } + + //dp[i] 保留最小的 + dp[i] = Math.min(dp[i],dp[i-coin] + 1 ) +} + +``` + +完整代码如下: + + +```Java +//递归解法,处理重叠子问题, 使用 dp[amount+1] 备忘录 + int coinChange2(int[] coins, int amount) { + int[] dp = new int[amount + 1]; + + // 数组大小为 amount + 1,初始值也为 amount + 1 + // 为啥 dp 数组初始化为 amount + 1 呢? + // 因为凑成 amount 金额的硬币数最多只可能等于 amount(全用 1 元面值的硬币),所以初始化为 amount + 1 就相当于初始化为正无穷 + Arrays.fill(dp, amount + 1); + + // base case + dp[0] = 0; + // 外层 for 循环在遍历所有状态的所有取值 + for (int i = 0; i < dp.length; i++) { + // 内层 for 循环在求所有选择的最小值 + for (int coin : coins) { + // 子问题无解,跳过 + if (i - coin < 0) { + continue; + } + dp[i] = Math.min(dp[i], 1 + dp[i - coin]); + } + } + return (dp[amount] == amount + 1) ? -1 : dp[amount]; + } + +``` + + +## 股票问题 + +num[i] 表示第 i 天的股票价格, 设计一个算法(交易策略) ,计算你能获得的最大收益,你最多可以完成 k 笔交易; + +比如: [3,2,6,5,1,3] +k=1 , 第2天买入 2块钱, 第3天卖出 6块,利润 6-2 = 4块 是最大利润 +k= 2 (可以交易2次) , [2,6] + [1,3] 是最大利润 + +动态规划思路如下: + + +* 状态定义: +* 状态转移: +* 状态压缩: + + +``` + +``` + + +## 接雨水问题 + +num[i]表示柱子高度,计算下雨之后最多能接多少雨水 + + +位置i能装多少? +位置 i 能达到的水柱高度和其左边的最高柱子、右边的最高柱子有关,我们分别称这两个柱子高度为 l_max 和 r_max;位置 i 最大的水柱高度就是 `min(l_max, r_max)` + + +思路: + +* 暴力解法 +* 备忘录解法 +* 双指针解法 + + +``` +``` + + + + + + + + + + + + + + + + + + + + + + + + + diff --git "a/8 Algorithms Analysis/\345\233\236\346\272\257\346\263\225.md" "b/8 Algorithms Analysis/\345\233\236\346\272\257\346\263\225.md" new file mode 100644 index 0000000..1a7fd91 --- /dev/null +++ "b/8 Algorithms Analysis/\345\233\236\346\272\257\346\263\225.md" @@ -0,0 +1,86 @@ +# 回溯法 + +也叫 `试探法`。 是一种选优搜索法,按照选优条件搜索,当搜索到某一步,发现原先选择并不优或达不到目标,就退回重新选择。 + +回溯算法其实就是我们常说的 DFS 算法,本质上就是一种暴力穷举算法。 + +一般步骤 + +1. 针对问题,定义解空间( 这时候解空间是一个集合,且包含我们要找的最优解) +2. 组织解空间,确定易于搜索的解空间结构,通常组织成`树结构` 或 `图结构` +3. 深度优先搜索解空间,搜索过程中用剪枝函数避免无效搜索 + +回溯法求解问题时,一般是一边建树,一边遍历该树;且采用非递归方法。 + + +#### 案例 + +* 迷宫问题 +* 全排列 + + + +## 代码框架 + +```python +result = [] +def backtrack(路径, 选择列表): + if 满足结束条件: + result.add(路径) + return + + for 选择 in 选择列表: + 做选择 + backtrack(路径, 选择列表) + 撤销选择 +``` + + + +## 全排列问题 + +> 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(); + } + } +``` + + + +## 八皇后问题 + +8x8的国际象棋棋盘上放置8个皇后,使得任何一个皇后都无法直接吃掉其他的皇后。任意2个皇后都不能处于同一个 横线,纵线,斜线上。 + +分析 + +1. 任意2个皇后不能同一行,也就是每个皇后占据一行,通用的,每个皇后也要占据一列 +2. 一个斜线上也只有一个皇后 + + + + + diff --git "a/Algorithms Analysis/\347\251\267\344\270\276\346\220\234\347\264\242\346\263\225.md" "b/8 Algorithms Analysis/\347\251\267\344\270\276\346\220\234\347\264\242\346\263\225.md" similarity index 94% rename from "Algorithms Analysis/\347\251\267\344\270\276\346\220\234\347\264\242\346\263\225.md" rename to "8 Algorithms Analysis/\347\251\267\344\270\276\346\220\234\347\264\242\346\263\225.md" index 783a715..2da99f3 100644 --- "a/Algorithms Analysis/\347\251\267\344\270\276\346\220\234\347\264\242\346\263\225.md" +++ "b/8 Algorithms Analysis/\347\251\267\344\270\276\346\220\234\347\264\242\346\263\225.md" @@ -1,6 +1,4 @@ - - -## 穷举搜索法 +# 穷举搜索法 或者叫蛮力法。对可能的解的众多候选按照某种顺序逐一枚举和检验。典型的问题如选择排序和冒泡排序。 diff --git "a/8 Algorithms Analysis/\350\264\252\345\277\203\347\256\227\346\263\225.md" "b/8 Algorithms Analysis/\350\264\252\345\277\203\347\256\227\346\263\225.md" new file mode 100644 index 0000000..400c5dd --- /dev/null +++ "b/8 Algorithms Analysis/\350\264\252\345\277\203\347\256\227\346\263\225.md" @@ -0,0 +1,19 @@ +# 贪心算法 + +不追求最优解,只找到满意解。 + + +#### 其它案例 + +* 跳跃游戏 +* 射击气球 +* 装箱问题 + + + +## 赫夫曼编码 + + + + + diff --git "a/Algorithms Analysis/\350\277\255\344\273\243\346\263\225.md" "b/8 Algorithms Analysis/\350\277\255\344\273\243\346\263\225.md" similarity index 95% rename from "Algorithms Analysis/\350\277\255\344\273\243\346\263\225.md" rename to "8 Algorithms Analysis/\350\277\255\344\273\243\346\263\225.md" index 8352728..41b808d 100644 --- "a/Algorithms Analysis/\350\277\255\344\273\243\346\263\225.md" +++ "b/8 Algorithms Analysis/\350\277\255\344\273\243\346\263\225.md" @@ -1,5 +1,4 @@ - -## 迭代法 +# 迭代法 是一种不断用旧值递推新值的过程,分精确迭代和近视迭代。是用来求方程和方程组近似根的方法。 diff --git "a/8 Algorithms Analysis/\351\200\222\345\275\222.md" "b/8 Algorithms Analysis/\351\200\222\345\275\222.md" new file mode 100644 index 0000000..761ed1e --- /dev/null +++ "b/8 Algorithms Analysis/\351\200\222\345\275\222.md" @@ -0,0 +1,260 @@ +# 递归 + +递归是一种设计和描述算法的有力工具。 也是回溯法和动态规划的基础。 + +递归算法执行过程分 `递推` 和 `回归` 两个阶段 + +在 `递推` 阶段,将大的问题分解成小的问题 +在 `回归` 阶段,获得最简单问题的解后,逐级返回,依次得到稍微复杂情况的解,知道获得最终的结果 + +1) 确定递归公式 , 比如 斐波那契数列 问题中的 `fib(n)=fib(n-1)+fib(n-2)` +2) 确定边界条件 bad case + +> 自顶向下的递归,自底向上是迭代 + + +递归运行效率较低,因为有函数调用的开销,递归多次也可能造成栈溢出。 + + +### 递归公式 + +[快排](../6%20Sort/README.md) + +```Java + void quicksort(int *a, int left, int right){ + if (left +``` + + +## 判断字符串是否是回文 + +> 回文,如 abcdcba + +分析: 2个指针,一头一尾,逐个比较,都相同就是回文 + +``` +/* +* eg acdeedca +* @ret 0 success , -1 fail +*/ +int is_huiwen(const char *source){ + if (source == NULL || source == '\0') return -1; + char *head = source; + char *tail = source; + while(*tail != '\0'){ + tail++; + } + tail--; + + while(head < tail){ + if (*head != *tail){ + return -1; + } + head++; + tail--; + } + + return 0; +} +``` + + + +## 统计文章里单词出现的次数 + +设计相应的数据结构和算法,尽量高效的统计一片英文文章(总单词数目)里出现的所有英文单词,按照在文章中首次出现的顺序打印输出该单词和它的出现次数。 + +``` +void statistics(char *string) +void statistics(FILE *fd) +``` + + +延伸: + +如果是海量数据里面统计top-k次数的单词呢? + + + +## 实现字符串转整型的函数 + +也就是实现函数atoi的功能,这道题目考查的就是对各种情况下异常处理。比如: + +以`213`分析转换成证书的过程。`3+1x10+2x100` ,思路是:每扫描到一个字符,把 `之前得到的数字*10`,再加上当前的数字 + +* 0开头,"0213" +* 正/负数,"-432" ,"--422","++23" +* 浮点数,"43.2344" +* 非法,"2123des" +* 存在空格," -32"," +432"," 234","23 432","353 "," + 321" +* NULL/空串,这时候返回值0 +* 溢出,"32111111112222222222222222222222222222222" , 与 `INT_MAX `比较 +* 如何区分正常的'0'和异常情况下返回的结果"0"? 可以通过一个全局变量 g_status 来标示,值为 kValid/kInvalid。 + + + +``` +int atoi(const char *str){ + +} +``` + +详细过程也可以[参考这里](http://blog.csdn.net/v_july_v/article/details/9024123) + + + + +## 匹配兄弟字符串 + +什么是兄弟字符串? + +如果两个字符串的字符一样,但是顺序不一样,被认为是兄弟字符串,问如何在迅速匹配兄弟字符串(如,bad和adb就是兄弟字符串)。 + +思路:判断各自素数乘积是否相等。更多方法请参见:http://blog.csdn.net/v_JULY_v/article/details/6347454。 + +``` +int isBrother(const char *first,const char *secd) +``` + +思路一: 循环匹配 指数级复杂度 +思路二: 利用质数,平方和比较,但这样必须是2个串的长度要一样,需要的空间比较大,最多256个字节。 + + + +示例代码: + +``` +int isBrother(const char *first,const char *secd){ + +} +``` + + + +## 字符串的排列 + +题目:输入一个字符串,打印出该字符串中字符的所有排列。例如输入字符串abc,则输出由字符a、b、c所能排列出来的所有字符串abc、acb、bac、bca、cab和cba。输入字符串 abcca,则输出由 a,b,c排列出来的所有字符串,字符出现个数不变 + + +分析:这是一道很好的考查对递归理解的编程题 + +简单的回溯就可以实现了。当然排列的产生也有很多种算法,去看看组合数学,还有逆序生成排列和一些不需要递归生成排列的方法。 + +>印象中Knuth的第一卷里面深入讲了排列的生成。这些算法的理解需要一定的数学功底,也需要一定的灵感,有兴趣最好看看。 + + + +## n个字符串联接 + +有n个长为m+1的字符串,如果某个字符串的最后m个字符与某个字符串的前m个字符匹配,则两个字符串可以联接,问这n个字符串最多可以连成一个多长的字符串,如果出现循环,则返回错误。 + + + + +## 字符串的集合合并 + +给定一个字符串的集合,格式如:{aaa bbb ccc}, {bbb ddd},{eee fff},{ggg},{ddd hhh}要求将其中交集不为空的集合合并,要求合并完成后的集合之间无交集,例如上例应输出{aaa bbb ccc ddd hhh},{eee fff}, {ggg}。 + + + + + + + diff --git "a/9 Algorithms Job Interview/1.1 \345\255\227\347\254\246\344\270\262-\346\237\245\346\211\276.md" "b/9 Algorithms Job Interview/1.1 \345\255\227\347\254\246\344\270\262-\346\237\245\346\211\276.md" new file mode 100644 index 0000000..dc80de2 --- /dev/null +++ "b/9 Algorithms Job Interview/1.1 \345\255\227\347\254\246\344\270\262-\346\237\245\346\211\276.md" @@ -0,0 +1,275 @@ +#字符串-查找 + +* 子串查找 + + + +## 找到第一个只出现一次的字符 + +在一个字符串中找到第一个只出现一次的字符。如输入ahbaccdeff,则输出h。 + +``` +char char_first_appear_once(const char *source) +``` + +思路一: 蛮力统计, O(n^2)复杂度 +思路二: 使用hash表,2次扫描 +* 第一次建立hash表 key为字符,value为出现次数; +* 第二次扫描找到第一个value为1的key,时间复杂度O(n) + +hash表长度 256,字符直接作为key值。需要注意的是 char 的范围是 -128~127,unsigned char 才是0~255 + + +示例代码: + +``` +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; + tmp++; + } + return '\0'; +} +``` + + +题目扩展:这里的字符换成整数,整数数量几十TB,海量数据处理,显然hash方法不可能,没有那么大得内容 + + + +### 字符串中找出连续最长的数字子串 + +写一个函数,功能: + +在字符串中找出连续最长的数字串,并把这个串的长度返回,并把这个最长数字串赋值给其中一个函数参数outputstr所指内存。 + +例如:"abcd12345ed125ss123456789"的首地址传给intputstr后,函数将返回9,outputstr所指的值为123456789 + +它的原形是: + +``` +int longest_continuious_number(const char *input,char *output) +``` + +应该有3个指针,第一个指针指向一个当前最长数字串的第一个数字,第二个指针指向第二个数字串的第一个数字,第三个指针是遍历指针,且统计第二个数字串的长度;当统计出来的长度大于第一个数字串的长度,第一个指针指向第二个指针指向的数字,相反,第二个指针和第三个指针继续向后查找。 + + +1. 当end首次碰到数字时,且tmp=0,说明是首次出现数字,第二个指针移到该数字,继续遍历 +2. 如果数字后面还是数字,tmp!=0 就是第二个数字串,因此 tmp += 1; +3. 当end从数字到普通字符时,如果tmp > max ,就要修改max和第一个指针start ,并把tmp归为0 + + +``` +int longest_continuious_number(const char *input,char *output){ + int max = 0; + char *start= input; + char *mid = input; + char *end = input; + int tmp = 0; + if (input == NULL || output == NULL) return 0; + + while (*end != '\0'){ + if (*end < '0' || *end < '9'){//字母 + if(tmp > max){ + max = tmp; + start = mid; + } + tmp = 0; + }else{//数字 + if (tmp == 0){//发现数字 + mid = end; + } + tmp++; + } + end++; + } + + //修改已数字结尾的bug + if(tmp > max){ + max = tmp; + start = mid; + } + + //copy + int i=0; + while(i window; + + int left = 0, right = 0; + int res = 0; // 记录结果 + while (right < s.size()) { + char c = s[right]; + right++; + // 进行窗口内数据的一系列更新 + window[c]++; + // 判断左侧窗口是否要收缩 + while (window[c] > 1) { + char d = s[left]; + left++; + // 进行窗口内数据的一系列更新 + window[d]--; + } + // 在这里更新答案 + res = max(res, right - left); + } + return res; +} +``` + + + +## 对称子字符串的最大长度 (最长回文子串) + +题目:输入一个字符串,输出该字符串中对称的子字符串的最大长度。比如输入字符串“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" + + + + + + +## 最长公共子串问题 + +请编写一个函数,输入两个字符串,求它们的最长公共子串,并打印出最长公共子串。 + +例如:输入两个字符串BDCABA和ABCBDAB,字符串BCBA和BDAB都是是它们的最长公共子串,则输出它们的长度4,并打印任意一个子串。 + +``` +int longest_common_subsequence(const char *s1,const char *s2, char *common) +``` + +分析:求最长公共子串(Longest Common Subsequence,LCS)是一道非常经典的动态规划题,因此一些重视算法的公司像MicroStrategy都把它当作面试题。如"abccade","dgcadde"的最大子串为"cad" + + + +实例代码: + +``` +int longest_common_subsequence(const char *s1,const char *s2, char *common){ + +} +``` + + + +## 求最大连续递增数字子串 + +如“ads3sl456789DF3456ld345AA”中的“456789”就是所求。这道题在上一道题目的基础上增加了数字要递增的条件。思路跟上面差不多,碰到不递增的数字就相当于第二个数字串了。 + + +## 请编写能直接实现strstr()函数功能的代码。 + +> strstr(str1,str2) 判断str2是否是str1的子串。 + +``` +/* +@ret 有就返回第一次出现子串的地址,否则返回NULL +*/ +char *strstr(const char *source, const char *target){ + +} +``` + + + +## 子串匹配的个数 + +已知一个字符串,比如asderwsde,寻找其中的一个子字符串比如sde的个数,如果没有返回0,有的话返回子字符串的个数。 + +``` +char *substr_count(const char *src, const char *substr, int *count) +``` + + + diff --git "a/9 Algorithms Job Interview/1.2 \345\255\227\347\254\246\344\270\262-\345\210\240\351\231\244.md" "b/9 Algorithms Job Interview/1.2 \345\255\227\347\254\246\344\270\262-\345\210\240\351\231\244.md" new file mode 100644 index 0000000..0f82a6e --- /dev/null +++ "b/9 Algorithms Job Interview/1.2 \345\255\227\347\254\246\344\270\262-\345\210\240\351\231\244.md" @@ -0,0 +1,71 @@ +# 字符串-删除 + + + +### 删除串中指定的字符 + +删除指定的字符以后,后面的字符都要向前移动一位。这种复杂度是O(N^2);那么有没有O(N)的方法呢? + +比如 "abcdeccba" 删除字符 "c"。使用2个指针,一前一后,比较前面的指针和删除字符: + +1. 不相等,两个指针一起跑,且前面的指针值拷贝到后面指针指向的空间 +2. 相等时,快指针向前一步 + +``` +char *delete_occurence_character(char *src , char target){ + char *front = src; + char *rear = src; + while(*front != '\0'){ + if (*front != target){ + *rear = *front; + rear++; + } + front++; + } + *rear = '\0'; + return src; +} +``` + + +### 在字符串中删除特定的字符 + +题目:输入两个字符串,从第一字符串中删除第二个字符串中所有的字符。例如,输入”They are students.”和”aeiou”,则删除之后的第一个字符串变成”Thy r stdnts.”。 + +这是上个题目的升级版本。 + +``` +char *delete_occurence_characterset(char *source,const char *del); +``` + +1. 蛮力法。 遍历字符串,每个字符去删除字符串集合中查找,有就删除 +2. 使用上面的方式,一次遍历 + + + +### 删除字符串中的数字并压缩字符串 + +如字符串”abc123de4fg56”处理后变为”abcdefg”。注意空间和效率。(下面的算法只需要一次遍历,不需要开辟新空间,时间复杂度为O(N)) +这道题跟上一道题也是一个意思。 + + +示例代码: + +``` +char *trim_number(char *source){ + char *start = source; + char *end = source; + if (source == NULL) return NULL; + while(*end != '\0'){ + if (*end < '0' || *end > '9' ){ + *start = *end; + start++; + } + end++; + } + *start = '\0'; + return source; +} +``` + + diff --git "a/9 Algorithms Job Interview/1.3 \345\255\227\347\254\246\344\270\262-\344\277\256\346\224\271.md" "b/9 Algorithms Job Interview/1.3 \345\255\227\347\254\246\344\270\262-\344\277\256\346\224\271.md" new file mode 100644 index 0000000..0fcf775 --- /dev/null +++ "b/9 Algorithms Job Interview/1.3 \345\255\227\347\254\246\344\270\262-\344\277\256\346\224\271.md" @@ -0,0 +1,178 @@ +# 字符串-修改 + + + +## 翻转句子中单词的顺序 + +题目:输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。句子中单词以空格符隔开。为简单起见,标点符号和普通字母一样处理。 +例如输入“I am a student.”,则输出“student. a am I”。 + +``` +char *revert_by_word(char *source); +``` + +思路: + +* 原地逆序,字符串2边的字符逐个交换 , 再按单词逆序; +* 也可以先按单词逆序,再对整个句子逆序; + +针对不允许临时空间的情况,也就是字符交换不用临时空间,可以使用的方法有: + +1. 异或操作 +2. 也就是2个整数相互交换一个道理 + +``` +char a = 'a', b = 'b'; +a = a + b; +b = a - b; +a = a - b; +``` + +最终示例代码: + +``` +//反转 +void _reverse(char *start,char *end){ + if ((start == NULL) || (end == NULL)) return; + while(start < end){ + char tmp = *start; + *start = *end; + *end = tmp; + + start++, + end--; + } +} + +char *revert_by_word(char *source){ + char *end = source; + char *start = source; + if (source == NULL) return NULL; + + //end指针挪动到尾部 + while (*end != '\0') end++; + end--; + + //先全部反转 + _reverse(start,end); + + //按单词反转 + start=end=source; + while(*start != '\0'){ + if (*start == ' '){ + start++; + end++; + }else if(*end == ' ' || *end == '\0'){ + _reverse(start,end-1); + start = end; + }else{ + end++; + } + } + return source; +} +``` + +类似的题目还有: + +不开辟用于交换数据的临时空间,如何完成字符串的逆序 +用C语言实现一个revert函数,它的功能是将输入的字符串在原串上倒序后返回。 + + + +## 替换空格 + +实现一个函数,把每个空格替换成 "%20",如输入“we are happy”,则输出“we%20are%20happy” + +``` +char *replce_blank(char *source) +``` + +主要问题是一个字符替换成3个字符,替换后的字符串比原串长。 +如果想要在原串上直接修改,就不能顺序替换。且原串的空间应该足够大,能容纳替换变长以后的字符串。如果空间不够,就要新建一块空间来保存替换的结果了。这里假设空间足够 + +1. 第一遍扫描,统计空格个数 n, 替换后的字符串长度 = 原长度+2*n +2. 从后向前扫描字符串,挪动每个字符的位置。注意碰到空格的地方 + + +``` +char *replace_blank(char *source){ + int count = 0; + char *tail = source; + if (source == NULL) return NULL; + while(*tail != '\0'){ + if (*tail == ' ') count++; + tail++; + } + + while(count){ + if(*tail != ' '){ + *(tail+2*count) = *tail; + }else{ + *(tail+2*count) = '0'; + *(tail+2*count-1) = '2'; + *(tail+2*count-2) = '%'; + count--; + } + tail--; + } + + return source; +} +``` + + +## 左旋转字符串 + +>字符串的左旋转操作:把字符串前面的若干个字符移动到字符串的尾部。 + +如把`字符串abcdef`左旋转2位得到`字符串cdefab` 。请实现字符串左旋转的函数。要求时间对长度为n的字符串操作的复杂度为O(n),辅助内存为O(1)。 + +``` +char *left_rotate(char *str,int offset){ + +} +``` + +思路: 我们可以abcdef分成两部分,ab和cdef,内部逆序以后,整体再次逆序,就可以得到想要的结果了。 +也就是跟上面的问题是同样的问题。 + + + + +## 字符串原地压缩 + +题目描述:“abeeeeegaaaffa" 压缩为 "abe5ag3f2a",请编程实现。 + +这道题需要注意: +1. 单个字符不压缩 +2. 注意考虑压缩后某个字符个数是多位数(超过10个) +3. 原地压缩最麻烦的地方就是数据移动 + + +这是使用2个指针,一前一后,如果不相等,都往前移动一位;如果相等,后一位变为数字2,且移动后面的指针一位,任然相等则数字加1,不相等 + + +``` +char *compress(const char *src,char *dest){ + +} +``` + +上面的压缩算法可以看到,压缩算法的`效率`验证依赖`给定字符串的特性`,如果'aaaaaaaa....aaa' 这样特征的字符串,使用上面的压缩算法,压缩率接近100%,相反,可能会的0%的压缩率。 + + + + + +## 编写strcpy 函数 + +已知strcpy 函数的原型是: +``` +char *strcpy(char *strDest, const char *strSrc); +``` +其中strDest 是目的字符串,strSrc 是源字符串。不调用C++/C 的字符串库函数 + + + + diff --git "a/9 Algorithms Job Interview/1.4 \345\255\227\347\254\246\344\270\262-\346\216\222\345\272\217.md" "b/9 Algorithms Job Interview/1.4 \345\255\227\347\254\246\344\270\262-\346\216\222\345\272\217.md" new file mode 100644 index 0000000..9bc9900 --- /dev/null +++ "b/9 Algorithms Job Interview/1.4 \345\255\227\347\254\246\344\270\262-\346\216\222\345\272\217.md" @@ -0,0 +1,53 @@ +# 字符串-排序 + + + +## 小写字母排在大写字母的前面 + +有一个由大小写组成的字符串,现在需要对他进行修改,将其中的所有小写字母排在大写字母的前面(大写或小写字母之间不要求保持原来次序),如有可能尽量选择时间和空间效率高的算法 c语言函数原型: + +``` +void proc(char *str) +``` +分析: + +比如:HaJKPnobAACPc,要小写字母前面且不要求保存顺序,可以是:anobcHJKPAACP + +1. 小写字母 a~z 的 ASCII码值是 97~122,A~Z的 ASCII码值是 65~90;0~9 的ASCII码是 48~57 +2. 两边向中间扫描,左边大写右边小写就交换;如果都小写,头指针向前知道找到大写;如果都是大写,尾指针向后找小写; + + +示例代码 + +``` +char *proc(char *str){ + char *start = str; + char *end = str; + if (str == NULL) return NULL; + while(*end != '\0') end++; + end--; + + while(start < end){ + if (*start >= 'A' && *start <= 'Z'){//大写 + if (*end >= 'a' && *end <= 'z'){ + char tmp = *start; + *start = *end; + *end = tmp; + + start++; + } + end--; + }else{//小写 + if (*end >= 'A' && *end <= 'Z'){ + end--; + } + start++; + } + } + return str; +} +``` + + + + diff --git "a/9 Algorithms Job Interview/2 \351\223\276\350\241\250.md" "b/9 Algorithms Job Interview/2 \351\223\276\350\241\250.md" new file mode 100644 index 0000000..e1fe717 --- /dev/null +++ "b/9 Algorithms Job Interview/2 \351\223\276\350\241\250.md" @@ -0,0 +1,131 @@ +# 链表 + +链表常常碰到的问题有: + + +* [链表排序](2.1%20链表-排序.md) +* [链表删除](2.2%20链表-删除.md) : 如删除链表中的p节点,在p节点前面插入节点q, 要求O(1)复杂度 +* [2个链表](2.3%20链表-2条.md) 相交,合并 +* 链表反转 +* 链表中是否有环 + + +链表结点定义如下: +``` +struct ListNode +{ + int m_nKey; + ListNode* m_pNext; +}; +``` + + + +常用解题思路 + +* 双指针 +* 3指针 +* 快慢指针 + + + +## 输出链表中倒数第k个节点 + +题目:输入一个单向链表,输出该链表中倒数第k个结点。链表的倒数第0个结点为链表的尾指针。 + +思路一:2遍遍历,先遍历一遍链表算出 n 的值,然后再遍历链表计算第 n - k 个节点 +思路二:双指针,一个指针先走k步,然后 两个指针一起同步往前走,前面先走的指针到链表尾部吧,后面的指针刚好是第k个节点 + + +类似的题目还有 + + `删除第k个节点` + `链表的中间结点` : 使用「快慢指针」的技巧 ,慢指针 slow 前进一步,快指针 fast 就前进两步,这样,当 fast 走到链表末尾时,slow 就指向了链表中点。 + + + +## 给定单链表,检测是否有环。 + +``` +int isLinkCicle(Link *head); +``` + +解题思路 + +1. 暴力 +2. 空间换时间,用一个HashMap +3. 快慢指针: 使用两个指针p1,p2从链表头开始遍历,p1每次前进一步,p2每次前进两步。如果p2到达链表尾部,说明无环,否则p1、p2必然会在某个时刻相遇(p1==p2),从而检测到链表中有环。 + + + + +## 链表反转 + + +思路一:迭代,三个指针,遍历一遍(0(n)复杂度 +思路一:递归实现,较难理解 + +迭代实现 + +``` +``` + + +递归实现 + +``` +ListNode reverse(ListNode head) { + if (head.next == null) return head; + ListNode last = reverse(head.next); + head.next.next = head; + head.next = null; + return last; +} +``` + + +## 从尾到头输出链表 + +输入一个链表的头结点,从尾到头反过来输出每个结点的值。链表结点定义如下: + +思路一: 辅助栈,需要一个栈空间 +思路二: 反转链表,然后遍历 +思路三: 递归实现,将printf语句放在递归调用后面。果然妙极。。 + + + + + +## 复杂链表的复制 + +题目:有一个复杂链表,其结点除了有一个m_pNext指针指向下一个结点外,还有一个m_pSibling指向链表中的任一结点或者NULL。其结点的C++定义如下: +``` +struct ComplexNode +{ + int m_nValue; + ComplexNode* m_pNext; + ComplexNode* m_pSibling; +}; +``` + +下图是一个含有5个结点的该类型复杂链表。图中实线箭头表示m_pNext指针,虚线箭头表示m_pSibling指针。为简单起见,指向NULL的指针没有画出。 +请完成函数`ComplexNode* Clone(ComplexNode* pHead)`,以复制一个复杂链表。 + + +这个题目难点在于: + + + + + + + + + + + + + + + + diff --git "a/9 Algorithms Job Interview/2.1 \351\223\276\350\241\250-\346\216\222\345\272\217.md" "b/9 Algorithms Job Interview/2.1 \351\223\276\350\241\250-\346\216\222\345\272\217.md" new file mode 100644 index 0000000..00283e2 --- /dev/null +++ "b/9 Algorithms Job Interview/2.1 \351\223\276\350\241\250-\346\216\222\345\272\217.md" @@ -0,0 +1,49 @@ +# 链表-排序 + + + + +## 链表排序 + +Given a head pointer pointing to a linked list ,please write a function that sort the list +in increasing order. You are not allowed to use temporary array or memory copy (微软面试题) + +``` +struct +{ + int data; + struct S_Node *next; +}Node; + +Node * sort_link_list_increasing_order (Node *pheader): +``` + + +## 单链表归并排序 + +啥是归并排序? + + + + +类似的题有: + +1 给定两个单链表(head1, head2),检测两个链表是否有交点,如果有返回第一个交点。 + + 如果head1==head2,那么显然相交,直接返回head1。 + 否则,分别从head1,head2开始遍历两个链表获得其长度len1与len2,假设len1>=len2, +那么指针p1由head1开始向后移动len1-len2步,指针p2=head2, +下面p1、p2每次向后前进一步并比较p1p2是否相等,如果相等即返回该结点, +否则说明两个链表没有交点。 + + +2 给定单链表(head),如果有环的话请返回从头结点进入环的第一个节点。 + + 运用题一,我们可以检查链表中是否有环。如果有环,那么p1p2重合点p必然在环中。从p点断开环,方法为:p1=p, p2=p->next, +p->next=NULL。此时,原单链表可以看作两条单链表,一条从head开始,另一条从p2开始, +于是运用题二的方法,我们找到它们的第一个交点即为所求。 + + + + + diff --git "a/9 Algorithms Job Interview/2.2 \351\223\276\350\241\250-\345\210\240\351\231\244.md" "b/9 Algorithms Job Interview/2.2 \351\223\276\350\241\250-\345\210\240\351\231\244.md" new file mode 100644 index 0000000..082149b --- /dev/null +++ "b/9 Algorithms Job Interview/2.2 \351\223\276\350\241\250-\345\210\240\351\231\244.md" @@ -0,0 +1,62 @@ +#链表-删除 + + + +## 在O(1)时间内删除链表结点 + +题目:给定链表的头指针和一个结点指针,在O(1)时间删除该结点。链表结点的定义如下: + +``` +struct ListNode{ + int m_nKey; + ListNode* m_pNext; +}; + +//函数的声明如下: +void deleteNode(ListNode* pListHead, ListNode* pToBeDeleted); + +``` + +思路: +保存下一个节点的值tmp,删除下一个节点,当前节点=tmp + + + + +## 删除链表中的p节点 + +只给定单链表中某个结点p(并非最后一个结点,即p->next!=NULL)指针,删除该结点。 + +办法很简单,首先是放p中数据,然后将p->next的数据copy入p中,接下来删除p->next即可。 + + +类似的还有问题:只给定单链表中某个结点p(非空结点),在p前面插入一个结点。办法类似,首先分配一个结点q,将q插入在p后, +接下来将p中的数据copy入q中,然后再将要插入的数据记录在p中。都可以做到0(1)复杂度 + + + + +## 将两链表中data值相同的结点删除 + + +有双向循环链表结点定义为: + + +``` +struct node{ + int data; + struct node *front,*next; +}; + +``` + +有两个双向循环链表A,B,知道其头指针为:pHeadA,pHeadB,请写一函数将两链表中data值相同的结点删除。 + + + + + + + + + diff --git "a/9 Algorithms Job Interview/2.3 \351\223\276\350\241\250-2\346\235\241.md" "b/9 Algorithms Job Interview/2.3 \351\223\276\350\241\250-2\346\235\241.md" new file mode 100644 index 0000000..7d78f3d --- /dev/null +++ "b/9 Algorithms Job Interview/2.3 \351\223\276\350\241\250-2\346\235\241.md" @@ -0,0 +1,151 @@ +# 链表-2条 + +链表的结点定义为: +``` +struct ListNode +{ + int m_nKey; + ListNode* m_pNext; +}; +``` + + +## 找出两个链表的第一个公共结点 + +题目:两个单向链表,找出它们的第一个公共结点。 + +分析:第一个公共节点,也就是2个链表中的节点的m_pNext 指向的同一个节点。 2遍遍历方法: + +1. 先遍历2个链表,得到各自的长度,和差sub +2. 长链表先遍历sub个节点,然后2个节点一起遍历 +3. 第一次同时指向的同一个节点就是这个commonNode + + +有没有可能一遍遍历就解决问题呢? + +让 p1 遍历完链表 A 之后开始遍历链表 B,让 p2 遍历完链表 B 之后开始遍历链表 A,这样相当于「逻辑上」两条链表接在了一起。 +空间复杂度为 O(1),时间复杂度为 O(N), 一遍遍历就搞定 + +``` +ListNode getIntersectionNode(ListNode headA, ListNode headB) { + // p1 指向 A 链表头结点,p2 指向 B 链表头结点 + ListNode p1 = headA, p2 = headB; + + while (p1 != p2) { + // p1 走一步,如果走到 A 链表末尾,转到 B 链表 + if (p1 == null) p1 = headB; + + else p1 = p1.next; + + // p2 走一步,如果走到 B 链表末尾,转到 A 链表 + if (p2 == null) p2 = headA; + else p2 = p2.next; + } + + return p1; +} +``` + + + +## 判断俩个链表是否相交 + +给出俩个单向链表的头指针,比如h1,h2,判断这俩个链表是否相交。为了简化问题,我们假设俩个链表均不带环。 + +问题扩展: + +1.如果链表可能有环列? +2.如果需要求出俩个链表相交的第一个节点列? + + + +## 合并2条有序链表 + +while 循环每次比较 p1 和 p2 的大小,把较小的节点接到结果链表上 + +``` +ListNode mergeTwoLists(ListNode l1, ListNode l2) { + // 虚拟头结点 + ListNode dummy = new ListNode(-1), p = dummy; + ListNode p1 = l1, p2 = l2; + + while (p1 != null && p2 != null) { + // 比较 p1 和 p2 两个指针 + // 将值较小的的节点接到 p 指针 + if (p1.val > p2.val) { + p.next = p2; + p2 = p2.next; + } else { + p.next = p1; + p1 = p1.next; + } + // p 指针不断前进 + p = p.next; + } + + if (p1 != null) { + p.next = p1; + } + + if (p2 != null) { + p.next = p2; + } + + return dummy.next; +} +``` + + +## 合并k个有序链表 + + +相比合并两个有序链表,难点在于,如何快速得到 k 个节点中的最小节点,接到结果链表上? + +就要用到 优先级队列(二叉堆) 这种数据结构,把链表节点放入一个最小堆,就可以每次获得 k 个节点中的最小节点: + + +``` +ListNode mergeKLists(ListNode[] lists) { + if (lists.length == 0) return null; + // 虚拟头结点 + ListNode dummy = new ListNode(-1); + ListNode p = dummy; + // 优先级队列,最小堆 + PriorityQueue pq = new PriorityQueue<>( + lists.length, (a, b)->(a.val - b.val)); + // 将 k 个链表的头结点加入最小堆 + for (ListNode head : lists) { + if (head != null) + pq.add(head); + } + + while (!pq.isEmpty()) { + // 获取最小节点,接到结果链表中 + ListNode node = pq.poll(); + p.next = node; + if (node.next != null) { + pq.add(node.next); + } + // p 指针不断前进 + p = p.next; + } + return dummy.next; +} +``` + + +## 输出两个非降序链表的并集 + +请修改append函数,利用这个函数实现: + +两个非降序链表的并集,1->2->3 和 2->3->5 并为 1->2->3->5 。另外只能输出结果,不能修改两个链表的数据。 + + + + + + + + + + diff --git "a/Algorithms Job Interview/\345\240\206\345\222\214\346\240\210.md" "b/9 Algorithms Job Interview/3 \345\240\206\345\222\214\346\240\210.md" similarity index 94% rename from "Algorithms Job Interview/\345\240\206\345\222\214\346\240\210.md" rename to "9 Algorithms Job Interview/3 \345\240\206\345\222\214\346\240\210.md" index 926492e..1ce24fa 100644 --- "a/Algorithms Job Interview/\345\240\206\345\222\214\346\240\210.md" +++ "b/9 Algorithms Job Interview/3 \345\240\206\345\222\214\346\240\210.md" @@ -1,5 +1,4 @@ - -##堆和栈 +# 堆和栈 栈的数据结构 @@ -46,7 +45,7 @@ void dequeue(Queue); 这里的题目是有关栈和队列的问题。 -#### 循环队列中元素个数 +## 循环队列中元素个数 如果用一个循环数组q[0..m-1]表示队列时,该队列只有一个队列头指针front,不设队列尾指针rear,求这个队列中从队列头到队列尾的元素个数(包含队列头、队列尾) @@ -67,7 +66,7 @@ else -#### 设计包含min函数的栈 +## 设计包含min函数的栈 定义栈的数据结构,要求添加一个min函数,能够得到栈的最小元素。 要求函数min、push以及pop的时间复杂度都是O(1)。 @@ -86,16 +85,14 @@ a < f(top),把 a push 到 辅助栈中 -#### 栈的push、pop序列 +## 栈的push、pop序列 题目:输入两个整数序列。其中一个序列表示栈的push顺序,判断另一个序列有没有可能是对应的pop顺序。为了简单起见,我们假设push序列的任意两个整数都是不相等的。 比如输入的push序列是[1、2、3、4、5] ,那么[4、5、3、2、1]就有可能是一个pop序列 因为可以有如下的push和pop序列: -【push 1,push 2,push 3,push 4,pop,push 5,pop,pop,pop,pop】 - -这样得到的pop序列就是【4、5、3、2、1】。 +【push 1,push 2,push 3,push 4,pop,push 5,pop,pop,pop,pop】, 这样得到的pop序列就是【4、5、3、2、1】。 但序列【4、3、5、1、2】就不可能是push序列【1、2、3、4、5】的pop序列。 @@ -114,7 +111,7 @@ a < f(top),把 a push 到 辅助栈中 -#### 颠倒栈 +## 颠倒栈 题目:用递归颠倒一个栈。例如输入栈{1, 2, 3, 4, 5},1在栈顶。颠倒之后的栈为{5, 4, 3, 2, 1},5处在栈顶。 diff --git "a/9 Algorithms Job Interview/4 \346\225\260\345\200\274\351\227\256\351\242\230.md" "b/9 Algorithms Job Interview/4 \346\225\260\345\200\274\351\227\256\351\242\230.md" new file mode 100644 index 0000000..2a11de2 --- /dev/null +++ "b/9 Algorithms Job Interview/4 \346\225\260\345\200\274\351\227\256\351\242\230.md" @@ -0,0 +1,150 @@ +# 数值问题 + +这部分都是一些数学几何计算方面的问题。主要由: + +* [加减乘除](4.1%20数值-加减乘除.md) +* 指数(乘方) +* 随机数 +* 进制转换:位运算 +* 大数问题 +* 公倍数 +* 素数 +* 丑数 + + +## ipv4 转 int + +比如: 127.0.0.1 , 转为int 为 (01111111 00000000 00000000 00000001) + +思路: + +``` +int toInt(){ + +} +``` + +那么,int 转 ipv4 如何解呢? + + + +## 整数的二进制表示中1的个数 + +题目:输入一个整数,求该整数的二进制表达中有多少个1。例如输入10,由于其二进制表示为1010,有两个1,因此输出2。 + +分析: +这是一道很基本的考查位运算的面试题。 +解法1:一轮循环移位计数 (移位运算比除法运算效率要高,注意要考虑是负数的情况) +解法2:位运算 +解法3:num &= num-1 巧妙之处在于,对高位没有影响。不断做 `num &= num-1` 直到num=0。 + +1010 & 1001 = 1000 +1000 * 0111 = 0000 + +``` +int one_appear_count_by_binary(int num){ + int count = 0; + while(num !=0 ){ + num &= num-1; + count++; + } + return count; +} +``` + + + +## 把十进制数(long型)分别以二进制和十六进制形式输出,不能使用printf系列 + +分析: +``` +char *integer_to_hex(long i); //eg: 20 => 14 +char *integer_to_bin(long i); //eg: 20 => 10100 +``` + +注意,转16进制中,要判断tmp[i]是否是有符号的数 +``` +tmp[i] = tmp[i]>=0 ? tmp[i] : tmp[i]+16; +``` + + + +## 请定义一个宏,比较两个数a、b的大小,不能使用大于、小于、if语句 + +分析: +``` +#define min(a,b) ((a)>(b)?(a):(b)) +#define MIN(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; }) +``` + +这里不能使用比较符号: + +``` +#define min(a,b) ((a)-(b) & (0x1<<31))?(a):(b) +``` + + + +## 整数的素数和分解问题 + +> 歌德巴赫猜想说任何一个不小于6的偶数都可以分解为两个奇素数之和。 +对此问题扩展,如果一个整数能够表示成两个或多个素数之和,则得到一个素数和分解式。 + +对于一个给定的整数,输出所有这种素数和分解式。 +注意,对于同构的分解只输出一次(比如5只有一个分解2 + 3,而3 + 2是2 + 3的同构分解式 + + +例如,对于整数8,可以作为如下三种分解: + +``` +(1) 8 = 2 + 2 + 2 + 2 +(2) 8 = 2 + 3 + 3 +(3) 8 = 3 + 5 +``` + + + + +## 输出1到最大的N位数 + +题目:输入数字n,按顺序输出从1最大的n位10进制数。比如输入3,则输出1、2、3一直到最大的3位数即999。 + +分析:这是一道很有意思的题目。看起来很简单,其实里面却有不少的玄机。 +输入4,输出: 1,2,3,。。9999 +输入5,输出: 1,2,3,4,...99999 + +玄机一: 整数溢出 + + + + +## 寻找丑数 + +> 我们把只包含因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含因子7。习惯上我们把1当做是第一个丑数。 +> 分析:这是一道在网络上广为流传的面试题,据说google曾经采用过这道题。 + +求按从小到大的顺序的第1500个丑数。 + + +这里的因子应该不包含本身,因此这个序列应该是这样: +1,2,3,4,5,6,8,9,10,12,15,16,18,20,28.... + + +1)所有的偶数都在序列中 +2)3的倍数也在序列中 +3)5的倍数也在系列中 + + +0. 2,3,5最小公倍数是30 +1. [1,30]符合条件有22个 +2. [30,60]符合条件也22个 + +第1500个: `1500/22=68` 余 4,一个周期内的前4个数是2,3,4,5; 最终答案是`68*30+5` + + + + + + + + diff --git "a/9 Algorithms Job Interview/4.1 \346\225\260\345\200\274-\345\212\240\345\207\217\344\271\230\351\231\244.md" "b/9 Algorithms Job Interview/4.1 \346\225\260\345\200\274-\345\212\240\345\207\217\344\271\230\351\231\244.md" new file mode 100644 index 0000000..5d6a05a --- /dev/null +++ "b/9 Algorithms Job Interview/4.1 \346\225\260\345\200\274-\345\212\240\345\207\217\344\271\230\351\231\244.md" @@ -0,0 +1,98 @@ +# 数值-加减乘除 + + +* 等差数列 +* 阶乘 + + + +## 求1+2+…+n + +要求不能使用乘除法、for、while、if、else、switch、case等关键字以及条件判断语句(A?B:C)。 + +思路:这是等差数列求和公式, 1+2+3.......+N =(n+1)n/2 + +分析: +1. 不能使用循环,那就用递归 +2. 递归需要终止递归的条件判断语句,这里也不能用if,想其他办法,可以使用 &&逻辑与 运算符(在n>0条件满足是,才会指向后面的递归语句) + +``` +int sum(n){ + int sum=0; + (n>0) && sum=n+factorial(n-1) + return sum; +} +``` + + + + +## 大数阶乘(factorial) + +> 阶乘 n*(n-1)*(n-2)*...*1 + +主要考虑算出来的结果肯定会大于int表达的范围,这时候怎么处理? + +``` +int factorial(n){ + int sum=0; + (n>0) && sum=n+factorial(n-1) + return sum; +} +``` + +考虑整数溢出情况 + +``` +char[] factorial(int n){ + int sum=0; + return sum; +} + +``` + + +## 1024! 末尾有多少个0? + +分析: +末尾0的个数取决于2和5的个数 +能被2整除的数比能被5整除的数要多得多,因此只统计被5整除的数的个数 + + + + +## 大整数乘法(或 大整数阶乘) + +请使用代码计算`1234567891011121314151617181920*2019181716151413121110987654321` 。 + +1. 注意结果可能超出长整形的最大范围 2^64-1 +2. 采用分治算法,将大整数相乘转换为小整数计算 + +规律分析:任意位数的整数相乘,最终都可以转化为2位数相乘 + + + + + + +## 实现两个正整数的除法(和取模) + +编程实现两个正整数的除法,当然不能用除法操作符。 +``` +int div(const int x, const int y) +``` + +1. 循环减被除数,减到不能再减,当除数很大,被除数小时,效率很低 +2. 位运算 + + + + +## 两个数相乘,小数点后位数没有限制,请写一个高精度算法 + + + + + + + diff --git "a/9 Algorithms Job Interview/4.2 \346\225\260\345\200\274-\346\214\207\346\225\260.md" "b/9 Algorithms Job Interview/4.2 \346\225\260\345\200\274-\346\214\207\346\225\260.md" new file mode 100644 index 0000000..247983c --- /dev/null +++ "b/9 Algorithms Job Interview/4.2 \346\225\260\345\200\274-\346\214\207\346\225\260.md" @@ -0,0 +1,126 @@ +# 4.2 数值-指数 + + +> 在a^n中,a叫做底数,n叫做指数。a^n读作“a的n次方”或“a的n次幂“ + + + +## 数值的整数次方 + +题目:实现函数`double Power(double base, int exponent)`,求base的exponent次方。 + +不需要考虑溢出。 + + +用例: + +``` +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) +{ + double result = 1.0; + for(int i = 1; i <= exponent; ++i) + result *= base; + return result; +} +``` + + +上面的代码没有考虑: exponent<=0 +判断一个浮点数是不是等于0时,不是直接写 base == 0 ,而应该判断它们的差的绝对值是不是小于一个很小的范围 + +如果指数大于0,我们还可以使用递归实现:`a^n = a^(n/2)*a^(n/2)` (n为偶数), 通过这个思路也可以实现 +``` +2^16 = 2^8 * 2^8 +2^8 = 2^4 * 2^4 +2^4 = 2^2 * 2^2 +2^2 = 2^1 * 2^1 +2^1 = 2 +``` + + +使用递归实现的代码示例: + +``` +double Power(double base, unsigned int exponent) +{ + if (exponent == 0) return 1; + if (exponent == 1) return base; + double result = Power(base, exponent >> 1); + result *= result; + + if (exponent & 1) result = result*base; + + return reslut; +} +``` + + +## 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...... 等。。 + +分析: +泰勒级数 +牛顿迭代法 + + + + + + +## 判断一个自然数是否是某个数的平方 + +说明:当然不能使用开方运算。 + +``` +square(25) YES +square(35) NO +``` + +方法1: 从1开始遍历,显然这种方法很差 +方法2: 除数跟余数比较,除数从2开始,每一轮都跟结果比较;相等就是存在这个数,不相等就把除数++;时间复杂度为(根号n)。在一个数比较大时,效率不够好。如1194877489 =(34567)^2,需要从2开始,一直比较到34566,做3万多次除法和比较运算。跟方法1一样。。 + +方法3:二分查找 O(logn)。比如25: + +a. 先取(0+25)/2=12.5,12.5*12.5>25,因此这个数应该小于12.5 +b.(0+12)/2 = 6, 6*6>25 +c. (0+6)/2 = 3 +d. (3+6)/2 = 4.5 +e. (5+6)/2 = 5.5 + +``` +int is_powered(int num) +``` + + + diff --git "a/9 Algorithms Job Interview/4.3 \346\225\260\345\200\274-\351\232\217\346\234\272\346\225\260.md" "b/9 Algorithms Job Interview/4.3 \346\225\260\345\200\274-\351\232\217\346\234\272\346\225\260.md" new file mode 100644 index 0000000..fc74070 --- /dev/null +++ "b/9 Algorithms Job Interview/4.3 \346\225\260\345\200\274-\351\232\217\346\234\272\346\225\260.md" @@ -0,0 +1,21 @@ +# 4.3 数值-随机数 + + + + +## 给定能随机生成整数1到5的函数,写出能随机生成整数1到7的函数。 + + + +## 设计一个随机算法 + +给你5个球,每个球被抽到的可能性为30、50、20、40、10,设计一个随机算法,该算法的输出结果为本次执行的结果。输出A,B,C,D,E即可。 + + +## 构造一个随机发生器 + +已知一随机发生器,产生0的概率是p,产生1的概率是1-p,现在要你构造一个发生器, +使得它构造0和1的概率均为1/2;构造一个发生器,使得它构造1、2、3的概率均为1/3;..., +构造一个发生器,使得它构造1、2、3、...n的概率均为1/n,要求复杂度最低。 + + diff --git "a/9 Algorithms Job Interview/4.4 \346\225\260\345\200\274-\346\234\200\345\260\217\345\205\254\345\200\215\346\225\260.md" "b/9 Algorithms Job Interview/4.4 \346\225\260\345\200\274-\346\234\200\345\260\217\345\205\254\345\200\215\346\225\260.md" new file mode 100644 index 0000000..7b835a1 --- /dev/null +++ "b/9 Algorithms Job Interview/4.4 \346\225\260\345\200\274-\346\234\200\345\260\217\345\205\254\345\200\215\346\225\260.md" @@ -0,0 +1,25 @@ +# 4.4 数值-最小公倍数 + + + +求两个或N个数的最大公约数和最小公倍数? + +> 最大公约数: 12、16的公约数有1、2、4,其中最大的一个是4,4是12与16的最大公约数 +> 最小公倍数: 45和30的最小公倍数是多少? +> 定理: (a,b)x[a,b]=ab ; (a,b) 是 a,b 的 最大公约数, [a,b]是a,b 的 最小公倍数 + + + +### 最小公倍数应用场景 + + +甲、乙、丙三人是朋友,他们每隔不同天数到图书馆去一次。甲3天去一次,乙4天去一次,丙5天去一次。有一天,他们三人恰好在图书馆相会,问至少再过多少天他们三人又在图书馆相会? + + +一块砖长20厘米,宽12厘米,厚6厘米。要堆成正方体至少需要这样的砖头多少块? + + +甲每秒跑3米,乙每秒跑4米,丙每秒跑2米,三人沿600米的环形跑道从同一地点同时同方向跑步,经过多少时间三人又同时从出发点出发? + + + diff --git "a/9 Algorithms Job Interview/4.5 \346\225\260\345\200\274-\347\264\240\346\240\221.md" "b/9 Algorithms Job Interview/4.5 \346\225\260\345\200\274-\347\264\240\346\240\221.md" new file mode 100644 index 0000000..d146256 --- /dev/null +++ "b/9 Algorithms Job Interview/4.5 \346\225\260\345\200\274-\347\264\240\346\240\221.md" @@ -0,0 +1,18 @@ +# 数值-素树 + +> 如果一个数如果只能被 1 和它本身整除,那么这个数就是素数 + + +## 返回区间 [2, n) 中有几个素数 + +``` +int countPrimes(int n) +``` + + + + + + + + diff --git "a/9 Algorithms Job Interview/5 \346\225\260\347\273\204\346\225\260\345\210\227\351\227\256\351\242\230.md" "b/9 Algorithms Job Interview/5 \346\225\260\347\273\204\346\225\260\345\210\227\351\227\256\351\242\230.md" new file mode 100644 index 0000000..e2b4756 --- /dev/null +++ "b/9 Algorithms Job Interview/5 \346\225\260\347\273\204\346\225\260\345\210\227\351\227\256\351\242\230.md" @@ -0,0 +1,255 @@ +# 数组数列问题 + +这部分的问题都集中在数据集合上。主要有: + +* 数组排序 +* top-k +* 子数组 +* 多个数组合并,交集 + + +解决这一类问题时,可以从以下几个方面考虑: + +* 万能的蛮力穷举 +* 散列表空间换事件 +* 分治法,然后归并 (归并排序) +* 选择合适的数据结构可以显著提高算法效率 (堆排序求top-k) +* 对无序的数组先排序,使用二分 +* 贪心算法或动态规划 + + + +## Fibonacci数列 + +题目:定义Fibonacci数列如下: +``` + 0 n=0 + f(n)= 1 n=1 + f(n-1)+f(n-2) n=2 +``` + +输入n,用最快的方法求该数列的第n项。 + +思路一:`递归`,虽然fibonacci数列是`递归`的经典应用,但递归效率很差,会有很多重复的计算,复杂度是成指数递增的,我测试了下计算50的时候已经要300s了。 + +``` +int fib(int N) { + if (N == 1 || N == 2) return 1; + return fib(N - 1) + fib(N - 2); +} +``` + +思路二:从下往上计算,复杂度O(N),一个循环就搞定 + +``` +public int fib(int n){ + if (n == 0) return 0; + + //缓存 + int[] dp = new int[n + 1]; + + // base case + dp[0] = 0; dp[1] = 1; + + // 状态转移 + for (int i = 2; i <= n; i++) { + dp[i] = dp[i - 1] + dp[i - 2]; + } + + return dp[n]; + } +``` + +说一个细节优化点,当前状态只和之前的两个状态有关,其实并不需要那么长的一个 DP table 来存储所有的状态,只要想办法存储之前的两个状态就行了。所以,可以进一步优化,把空间复杂度降为 O(1): + +``` +int fib(int n) { + if (n < 1) return 0; + if (n == 2 || n == 1) + return 1; + int prev = 1, curr = 1; + for (int i = 3; i <= n; i++) { + int sum = prev + curr; + prev = curr; + curr = sum; + } + return curr; +} +``` + + + + +## 递减数列左移后的数组中找数 + +一个数组是由一个递减数列左移若干位形成的,比如{4,3,2,1,6,5} +是由{6,5,4,3,2,1}左移两位形成的,在这种数组中查找某一个数。 + + +1. 右移,二分查找。 找到最小的数,右移到第一个位置的时候,右移完成 O(N) +2. 直接二分查找 + + + + +## 给出一个洗牌算法 + +给出洗牌的一个算法,并将洗好的牌存储在一个整形数组里 + +分析:扑克牌54张`2~10,J,Q,K,A,小王,大王` + +1)产生随机数, 随机数 rand()%54 ,rand()每次运行都一样,要改为srand(time(NULL)) +2) 遍历数组, 随机数k属于区间[i,n],然后a[i] 和 随机数 a[k] 对换 + + + +## 重合区间最长的两个区间段 + +在一维坐标轴上有n个区间段,求重合区间最长的两个区间段 +如【-7,21】,【4,23】,【14,100】,【54,76】 + +思路一:两两比较,复杂度 N^2 +思路二:先排序+分而治之 + + + + + +在一个int数组里查找这样的数,它大于等于左侧所有数,小于等于右侧所有数。 +直观想法是用两个数组a、b。a[i]、b[i]分别保存从前到i的最大的数和从后到i的最小的数, +一个解答:这需要两次遍历,然后再遍历一次原数组, +将所有data[i]>=a[i-1]&&data[i]<=b[i]的data[i]找出即可。 +给出这个解答后,面试官有要求只能用一个辅助数组,且要求少遍历一次。 + + + + +一排N(最大1M)个正整数+1递增,乱序排列,第一个不是最小的,把它换成-1, +最小数为a且未知。求第一个被-1替换掉的数原来的值,并分析算法复杂度。 + +[4,3,5,2,7,6] +题目啥意思? + + + +正整数序列Q中的每个元素都至少能被正整数a和b中的一个整除,现给定a和b,需要计算出Q中的前几项,例如,当a=3,b=5,N=6时,序列为3,5,6,9,10,12 +(1)、设计一个函数void generate(int a,int b,int N ,int * Q)计算Q的前几项 +(2)、设计测试数据来验证函数程序在各种输入下的正确性。 + +分析: +这个输出序列是要 递增排列 +思路一: 类似对2各数组merge,取min( A[i],B[j]) .复杂度O(N) +不过这里要注意,去掉 a, b的公倍数。如 3,5 都有15可以整除 + + + + + + +## 把数组排成最小的数 + +题目:输入一个正整数数组,将它们连接起来排成一个数,输出能排出的所有数字中最小的一个。 +例如输入数组{32, 321},则输出这两个能排成的最小数字32132。 +请给出解决问题的算法,并证明该算法。 + + + + +## 旋转数组中的最小元素。 + +题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 +输入一个排好序的数组的一个旋转,输出旋转数组的最小元素。 +例如数组{3, 4, 5, 1, 2}为{1, 2, 3, 4, 5}的一个旋转,该数组的最小值为1。 + +分析:这道题最直观的解法并不难。从头到尾遍历数组一次,就能找出最小的元素, +时间复杂度显然是O(N)。但这个思路没有利用输入数组的特性,我们应该能找到更好的解法。 + + + + + +## 约瑟夫环问题 + +n个数字(0,1,…,n-1)形成一个圆圈,从数字0开始, +每次从这个圆圈中删除第m个数字(第一个为当前数字本身,第二个为当前数字的下一个数字)。 +当一个数字删除后,从被删除数字的下一个继续删除第m个数字。 +求出在这个圆圈中剩下的最后一个数字。 + +如 `0,1,2,3,4,5` 删除第2个数字 (n=6,m=2) +第一次删除:1 +第二次删除: 3 +第三次删除:5 +第四次删除:2 +因此,左后的一个数字就是 4 + +从数学上分析下规律: + + + + + + +## 求最大重叠区间大小 + +题目描述:请编写程序,找出下面“输入数据及格式”中所描述的输入数据文件中最大重叠区间的大小。 +对一个正整数 n ,如果n在数据文件中某行的两个正整数(假设为A和B)之间,即A<=n<=B或A>=n>=B ,则 n 属于该行; +如果 n 同时属于行i和j ,则i和j有重叠区间;重叠区间的大小是同时属于行i和j的整数个数。 + +例如,行(10 20)和(12 25)的重叠区间为 [12 20] ,其大小为9,行(20 10)和( 20 30 )的重叠区间大小为 1 。 + + + + +## 四对括号可以有多少种匹配排列方式 + +比如两对括号可以有两种:()()和(()) + + + + +## 两两之差绝对值最小的值 + +有一个整数数组,请求出两两之差绝对值最小的值,记住,只要得出最小值即可,不需要求出是哪两个数。 +如 [-1, 3, 5, 9] 绝对值最小的是2(5-3) + +最短区间问题 +如果是有重复元素,那最小值就是0了 + +解题思路: 可以将这个问题转化为 ”求最大字段和“ 问题。。。 + + + +## 数值是否连续相邻 + +一个整数数列,元素取值可能是0~65535中的任意一个数,相同数值不会重复出现。0是例外,可以反复出现。请设计一个算法,当你从该数列中随意选取5个数值,判断这5个数值是否连续相邻。 +注意: +- 5个数值允许是乱序的。比如: 8 7 5 0 6 +- 0可以通配任意数值。比如:8 7 5 0 6 中的0可以通配成9或者4 +- 0可以多次出现。 +- 复杂度如果是O(n2)则不得分。 + +这个问题跟”扑克牌顺子“判断问题一样,通过比较0的个数和相邻数字之间间隔总和来判断所有数是否连续。 + + + + + +////////////// 数列 /////////////// + + +给出两个集合A和B,其中集合A={name}, +集合B={age、sex、scholarship、address、...}, + +要求: +问题1、根据集合A中的name查询出集合B中对应的属性信息; +问题2、根据集合B中的属性信息(单个属性,如age<20等),查询出集合A中对应的name。 + + + + + + + + + + diff --git "a/9 Algorithms Job Interview/5.1 \346\225\260\345\210\227-\346\216\222\345\272\217.md" "b/9 Algorithms Job Interview/5.1 \346\225\260\345\210\227-\346\216\222\345\272\217.md" new file mode 100644 index 0000000..b01a548 --- /dev/null +++ "b/9 Algorithms Job Interview/5.1 \346\225\260\345\210\227-\346\216\222\345\272\217.md" @@ -0,0 +1,126 @@ +# 5.1 数列-排序 + + +## 使用归并排序对一个 int 类型的数组排序 + + +比如 [1, 6, 2, 2, 2, 3] + +``` +void sort(int * a, int length) +``` + + + +## 用递归的方法判断整数组a[N]是不是升序排列 + +递归 isAscend(n-1) && a[N-1]< a[N] + + + + +## 求一个数组的最长递减子序列 + + 比如`{9,4,3,2,5,4,3,2}`的最长递减子序列为`{9,5,4,3,2}` + +`动态规划` + + + +## 扑克牌的顺子 + +从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。 +2-10为数字本身,A为1,J为11,Q为12,K为13,而大小王可以看成任意数字。 + +对这5个数(大小王看做0) +1)排序,如快排 +2)统计0的个数 +3)统计相邻元素空缺总数 + +``` +//5个是不是顺子 +isShunZi(int *a,int length) +``` + + + + +## 分割数组 + +一个int数组,里面数据无任何限制,要求求出所有这样的数a[i],其左边的数都小于等于它,右边的数都大于等于它。 +能否只用一个额外数组和少量其它空间实现。 + +`快速排序` + + + + + +## 调整数组顺序使奇数位于偶数前面 + +题目:输入一个整数数组,调整数组中数字的顺序,使得所有奇数位于数组的前半部分, +所有偶数位于数组的后半部分。要求时间复杂度为O(n)。 + +思路:两边向中间扫描,如果第一个指针是偶数,第二个指针是奇数,就交换;如果第一个是偶数,第二个也偶数,第二个指针向前移;反之,第一个指针向后移 + +``` +void reorder(int *data, int length); +``` + + + +## 奇偶分离 + +给定一个存放整数的数组,重新排列数组使得数组左边为奇数,右边为偶数。 +要求:空间复杂度O(1),时间复杂度为O(n) +如 [4,5,2,7,5] => [5,7,5,4,2], +空间复杂度O(1),得使用 交换排序 + +插入排序思想 +快速排序思想 + + + +1-1000放在含有1001个元素的数组中,只有唯一的一个元素值重复,其它均只出现一次. +每个数组元素只能访问一次,设计一个算法,将它找出来;不用辅助存储空间, +能否设计一个算法实现? + +分析:难点在于 `不用辅助空间`。 + +思路一: `sum(数组元素的总和)-sum(1~1000)` 得到的差即为重复元素,N较大时注意总和溢出 +思路二: 异或操作(位运算) + + + + + +## 重新排列使负数排在正数前面 + +一个未排序整数数组,有正负数,重新排列使负数排在正数前面,并且要求不改变原来的正负数之间相对顺序 + +比如: input: 1,7,-5,9,-12,15 ans: -5,-12,1,7,9,15 要求时间复杂度O(N),空间O(1)。(此题一直没看到令我满意的答案,一般达不到题目所要求的:时间复杂度O(N),空间O(1),且保证原来正负数之间的相对位置不变)。 + +updated:设置一个起始点j, 一个翻转点k,一个终止点L +从右侧起 +起始点在第一个出现的负数, 翻转点在起始点后第一个出现的正数,终止点在翻转点后出现的第一个负数(或结束) +如果无翻转点, 则不操作 +如果有翻转点, 则待终止点出现后, 做翻转, 即ab => ba 这样的操作 +翻转后, 负数串一定在左侧, 然后从负数串的右侧开始记录起始点, 继续往下找下一个翻转点 + +例子中的就是 + +1, 7, -5, 9, -12, 15 +第一次翻转: 1, 7, -5, -12,9, 15 => 1, -12, -5, 7, 9, 15 +第二次翻转: -5, -12, 1, 7, 9, 15 + + N维翻转空间占用为O(1)复杂度是2N;在有一个负数的情况下, 复杂度最大是2N, ;在有i个负数的情况下, 复杂度最大是2N+2i, 但是不会超过2N+N实际的复杂度在O(3N)以内 + 但从最终时间复杂度分析,此方法是否真能达到O(N)的时间复杂度,还待后续考证。感谢John_Lv,MikovChain。2012.02.25。 + +1, 7, -5, -6, 9, -12, 15(后续:此种情况未能处理) +1 7 -5 -6 -12 9 15 +1 -12 -5 -6 7 9 15 +-6 -12 -5 1 7 9 15 + +更多请参考此文,程序员编程艺术第二十七章:重新排列数组(不改变相对顺序&时间O(N)&空间O(1),半年未被KO)http://blog.csdn.net/v_july_v/article/details/7329314。 + + diff --git "a/9 Algorithms Job Interview/5.2 \346\225\260\345\210\227-nsum\351\227\256\351\242\230.md" "b/9 Algorithms Job Interview/5.2 \346\225\260\345\210\227-nsum\351\227\256\351\242\230.md" new file mode 100644 index 0000000..d76c014 --- /dev/null +++ "b/9 Algorithms Job Interview/5.2 \346\225\260\345\210\227-nsum\351\227\256\351\242\230.md" @@ -0,0 +1,228 @@ +#数列-nsum问题 + + +## 数组,找出和为 s 的两个数 + +暴力解法 + +``` +class Solution { + public int[] twoSum(int[] nums, int target) { + int res[] = new int[2]; + for(int i = 0; i < nums.length; i++){ + for(int j = i + 1; j < nums.length; j++){ + if(nums[i] + nums[j] == target){ + res[0] = i; + res[1] = j; + break; + } + } + } + return res; + } +} +``` + +使用 hash表,2遍扫描 + +``` Java +class Solution { + public int[] twoSum(int[] nums, int target) { + int res[] = new int[2]; + Map map= new HashMap<>(); + for(int i = 0; i < nums.length; i++){ + map.put(nums[i], i); + } + for(int i = 0; i < nums.length; i++){ + int temp = target - nums[i]; + if(map.containsKey(temp) && map.get(temp) != i){ + res[0] = map.get(temp); + res[1] = i; + } + } + return res; + } +} + +``` + + +## 找出和为N+1的2个数 + +一个整数数列,元素取值可能是`1~N`(N是一个较大的正整数)中的任意一个数,相同数值不会重复出现。 +设计一个算法,找出数列中符合条件的数对的个数,满足数对中两数的和等于`N+1`。 +复杂度最好是 `O(n)` ,如果是 `O(n2)`则不得分。 + + +分析:列出所有的数对,如输入15,输出`【1,14】【2,13】【3,12】。。。` + +``` +int print_sequence_sum(int n) +``` + + +## 找出和为m的2个数 + +输入一个已经按升序排序过的数组和一个数字,在数组中查找两个数,使得它们的和正好是输入的那个数字。 +要求时间复杂度是O(n)。如果有多对数字的和等于输入的数字,输出任意一对即可。 +例如输入数组【1、2、4、7、11、15=和数字15。由于4+11=15,因此输出4和11。 + + +``` +//返回0找到,返回-1没找到 +int findaddends(int *data,int length,int sum,int *a,int *b); +``` + +分析: +数组升序排列,查找可用二分查找,时间复杂度`O(logn)`,这样问题就变成了找其中一个加数的问题,复杂度为`N*logN` + +巧妙解法:数组2端向中间扫描,复杂度O(N) + + + + +## 求子数组的最大和(最大字段和) + +输入一个整形数组,数组里有正数也有负数。 +数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。 +求所有子数组的和的最大值。要求时间复杂度为O(n)。 + +例如输入的数组为`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保存最终的最大和; 如果都是负数 +``` + int maxSumOfVector(int *data,int length){ + + int max=0; + int maxendinghere = 0; + + if(data==NULL || length<=0){ + return 0; + } + for(int i=0;i < length, i++){ + + maxendinghere+=data[i]; + if(maxendinghere<0){ + maxendinghere=0; + continue; + } + + if(maxendinghere>max){ + max=maxendinghere; + } + + } + + return max; + } +``` + + + + + +## 和为n连续正数序列 + +题目:输入一个正数n,输出所有和为n连续正数序列。 + +例如输入15,由于1+2+3+4+5=4+5+6=7+8=15,所以输出3个连续序列1-5、4-6和7-8。 + +``` +print_continuous_sequence_sum(int n) +``` + +思路一:枚举法。从1开始一直加到等于n,再从2开始。。一直到 n/2+1,在每一轮,都要逐步比较。复杂度`O(N^2)` +思路二: a[small,big] sum[small,big]>N small往前移动,否则,big往前移动。 `O(N)` 复杂度搞定 + + + + +## 求连续子数组和为m的组合 + +数组 [2,3,1,2,4,3]求连续子数组sum=7, 结果[1,2,4] [3,4] + +* 暴力遍历 +* 滑动窗口, 左指针,右指针 + +``` +if sum= 0 && index_b >= 0) { + + //2个指针都有值时 + if (a[index_a] >= 0 && b[index_b] >= 0) { + if (a[index_a] > b[index_b]) { + a[len_a--] = a[index_a--]; + }else{ + a[len_a--] = b[index_b--]; + } + } + + //a 无值,b有值 ; 把剩下b放好 + if (index_a < 0 && index_b >= 0) { + while (index_b >= 0) { + a[len_a--] = b[index_b--]; + } + } + + //a 有值,b无值; 把剩下a放好 + if (index_a >=0 && index_b < 0) { + while (index_b >= 0) { + + } + } + } + } +``` + + +## 两个序列和之差最小 + +有两个序列a,b,大小都为n,序列元素的值任意整数,无序; +要求:通过交换a,b中的元素,使[序列a元素的和]与[序列b元素的和]之间的差最小。 + +例如: +``` +var a=[100,99,98,1,2, 3]; +var b=[1, 2, 3, 4,5,40]; +``` + + + diff --git "a/9 Algorithms Job Interview/5.4 \346\225\260\345\210\227-\346\237\245\346\211\276.md" "b/9 Algorithms Job Interview/5.4 \346\225\260\345\210\227-\346\237\245\346\211\276.md" new file mode 100644 index 0000000..24cf68b --- /dev/null +++ "b/9 Algorithms Job Interview/5.4 \346\225\260\345\210\227-\346\237\245\346\211\276.md" @@ -0,0 +1,226 @@ +# 数列-查找 + + +## 数组中超过出现次数超过一半的数字 + +题目:数组中有一个数字出现的次数超过了数组长度的一半,找出这个数字。 + +`+-1 计数法` + + + +## 找数组里面重复的一个数 + +找数组里面重复的一个数,一个含n个元素的整数数组至少存在一个重复数,请编程实现,在O(n)时间内找出其中任意一个重复数。 + +1. hash算法,空间要求多 (注意数组元素要是int类型),数的大小范围和数组长度N都可以是无穷大,这里使用hash算法,空间复杂度是O(n),空间可能无法满足条件。 +2. 先排序,复杂度n(logn);然后遍历一遍就可以知道哪些数重复了。 +2. 高级解法: 将问题转化为 `判断单链表中存在环` + + +类似问题:找出数组中唯一的重复元素 + + +## 查找只出现一次的元素 + +给一个非空整数数组,比如 [2, 2, 3] ,其余元素均出现2次,找出那个只出现一次的元素。 + +思路: 异或运算,成对儿的数字就会变成 0,落单的数字和 0 做异或还是它本身 + +``` +int singleNumber(vector& nums) { + int res = 0; + for (int n : nums) { + res ^= n; + } + return res; +} +``` + + +## 数组中找出某数字出现的次数 + +在排序数组中,找出给定数字的出现次数。比如 [1, 2, 2, 2, 3] 中2的出现次数是3次。 + +分析: +1. 因为是排序数组,可以使用二分查找 +2. 将二分查找坚持到底,这样在最坏的情况下([2,2,2,2,2,2,2])都有0(lgn)复杂度 + +``` +int binary_search_first(int *a,int length,int key); +int binary_search_lash(int *a,int length,int key); +``` + + + + +## 大于K的最小正整数 + +给定一个集合A=[0,1,3,8](该集合中的元素都是在0,9之间的数字,但未必全部包含),指定任意一个正整数K,请用A中的元素组成一个大于K的最小正整数。 + +比如,A=[1,0] K=21 那么输出结构应该为100。 + + + + + +## 查找最小的k个元素(top-k) + +题目:输入n个整数,输出其中最小的k个。 +例如输入1,2,3,4,5,6,7和8这8个数字,则最小的4个数字为1,2,3和4。 + +``` +topMinK(int *a,int length,int k); +topMaxK(int *a,int length, int k); +``` + + +1. 全部排序 复杂度 NlogN , 数据了较大时,内存可能承受不住 +2. 部分排序 维护一个大小为K的数组,由大到小排序,然后遍历所有数据,每个数据跟数组中最小元素比较,如果比最小元素大,就要插入数组了,这里还有寻找插入位置,移动数组元素的cpu消耗。复杂度是N*K +3. 堆排序 。在这的K较大时(比如这道题目:2亿个整数中求最大的100万之和),上面的算法还是有很多可以改进的地方,如采用二分查找定位插入位置,移动数组元素的计算是躲不过去了。那有没有什么数据结构即能`快速查找,还能快速移动元素`呢?最好是O(1)复杂度。 + +答案就是`二叉堆`。我们可以遍历总量中的每个元素来跟二叉堆中的堆顶元素比较(堆顶元素在`小根堆`中最小值,在`大根堆`中是最大值),这样在0(1)复杂度就可以完成查找操作,揭下来需要的操作就是重新调整推结构,复杂度是O(logk),因此整个操作复杂度是 `O(n*logk)` + + +`top-k 小的时候用 *大根堆* ,top-k 大得时候用 *小根堆*` + + +## 找第k大的数 + +比如 `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 是n个不同的实数的序列,L的递增子序列是这样一个子序列 +Lin=< aK1,ak2,…,akm >,其中k1< k2<…< km且aK1< ak2<…< akm。 +求最大的m值。 + +如【5,6,7,3,2,8】 最长子序列 【5,6,7,8】 + +`动态规划` + + + + + +## 在从1到n的正数中1出现的次数 + +题目:输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数。 + +例如输入12,从1到12这些整数中包含1 的数字有1,10,11和12,1一共出现了5次。 +分析:这是一道广为流传的google面试题。 +``` +int one_appear_count(int n); +``` + +思路1. 遍历`1~n`,统计出现1的个数;n足够大时,效率很低 +思路2. 分析规律 + + + +## 搜索旋转排序数组 + + +整数数组 nums 按升序排列,数组中的值 互不相同 。 + +在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 在下标 3 处经旋转后可能变为 [4,5,6,7,0,1,2] 。 + +给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。 + + +示例 1: + +``` +输入:nums = [4,5,6,7,0,1,2], target = 0 +输出:4 +``` + +示例 2: +``` +输入:nums = [4,5,6,7,0,1,2], target = 3 +输出:-1 +``` + + + + + + + + + + diff --git "a/Algorithms Job Interview/\347\237\251\351\230\265.md" "b/9 Algorithms Job Interview/6 \347\237\251\351\230\265.md" similarity index 77% rename from "Algorithms Job Interview/\347\237\251\351\230\265.md" rename to "9 Algorithms Job Interview/6 \347\237\251\351\230\265.md" index c6e264d..8f6e1d0 100644 --- "a/Algorithms Job Interview/\347\237\251\351\230\265.md" +++ "b/9 Algorithms Job Interview/6 \347\237\251\351\230\265.md" @@ -1,38 +1,9 @@ - +# 矩阵 矩阵在计算机中表示就是二维数组。这部分内容都是有关二维数组和矩阵相关的题目。 -#### 二维数组中的查找 - -在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。 - -``` -int exist_inmatrix(int *matrix,int rows,int cols,int num); -``` - -#### 矩阵中最大的二维矩阵 - -求一个矩阵中最大的二维矩阵(元素和最大).如: -``` -1 2 0 3 4 -2 3 4 5 1 -1 1 5 3 0 -``` -中最大的是: -``` -4 5 -5 3 -``` - -要求:(1)写出算法;(2)分析时间复杂度;(3)用C写出关键代码 - -``` -int max_sum_submatrix(int *matrix,int rows,int cols,int *tmatrix,int *trows,int tcols) -``` - - -#### 顺时针打印矩阵 +## 顺时针打印矩阵 题目:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。 例如:如果输入如下矩阵: @@ -46,6 +17,7 @@ int max_sum_submatrix(int *matrix,int rows,int cols,int *tmatrix,int *trows,int 则依次打印出数字`1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5, 6, 7, 11, 10` 。 分析:包括Autodesk、EMC在内的多家公司在面试或者笔试里采用过这道题。 +难点: 各种边界条件判断,很容易搞错 一圈一圈打印,第一圈origin是(0,0),第二圈是(1,1) @@ -100,7 +72,7 @@ void print_matrix_incircle(int *matrix,int rows,int cols,int start){ -#### 从小到大输出矩阵的值 +## 从小到大输出矩阵的值 思路1:采用归并进行排序然后进行顺序打印 @@ -125,6 +97,7 @@ void printArry(int *a,int rows,int columns) int* pArr1=new int[rows];//共存有这么多行指针 for(int i=0;i Google 面试题, 此题采用bfs和拓扑排序均可达到面试官的要求。笔者认为,一般的bfs可以达到hire;记忆化搜索和拓扑排序可以达到strong hire + + + -#### n支队伍比赛的名次 -n支队伍比赛,分别编号为0,1,2。。。。n-1,已知它们之间的实力对比关系,存储在一个二维数组w[n][n]中,w[i][j] 的值代表编号为i,j的队伍中更强的一支。 -所以w[i][j]=i 或者j,现在给出它们的出场顺序,并存储在数组order[n]中, -比如order[n] = {4,3,5,8,1......},那么第一轮比赛就是 4对3, 5对8。....... -胜者晋级,败者淘汰,同一轮淘汰的所有队伍排名不再细分,即可以随便排,下一轮由上一轮的胜者按照顺序,再依次两两比,比如可能是4对5,直至出现第一名 -编程实现,给出二维数组w,一维数组order 和 用于输出比赛名次的数组result[n],求出result。 diff --git "a/9 Algorithms Job Interview/7 \344\272\214\345\217\211\346\240\221.md" "b/9 Algorithms Job Interview/7 \344\272\214\345\217\211\346\240\221.md" new file mode 100644 index 0000000..28bcff2 --- /dev/null +++ "b/9 Algorithms Job Interview/7 \344\272\214\345\217\211\346\240\221.md" @@ -0,0 +1,177 @@ +# 二元树 + +问题涉及有: + +* 遍历 +* 翻转 +* 子树 + + +写二叉树的算法题,都是基于递归框架的。我们先要搞清楚 root 节点它自己要做什么,然后根据题目要求选择使用前序,中序,后续的递归框架。 + + +二元树的结点定义如下: +``` +struct SBinaryTreeNode // a node of the binary tree +{ + int m_nValue; // value of node + SBinaryTreeNode *m_pLeft; // left child of node + SBinaryTreeNode *m_pRight; // right child of node +}; +``` + +二叉树递归框架 + +```Java + +/* 二叉树遍历框架 */ +void traverse(TreeNode root) { + // 前序遍历 + traverse(root.left) + // 中序遍历 + traverse(root.right) + // 后序遍历 +} + +``` + + + +## 二叉树翻转 + +输入一颗二元查找树,将该树转换为它的镜像,即在转换后的二元查找树中,左子树的结点都大于右子树的结点。用递归和循环两种方法完成树的镜像转换。例 + +如输入: +``` + 8 + / \ + 6 10 + /\ / \ +5 7 9 11 +``` +输出: + +``` + 8 + / \ + 10 6 + /\ /\ +11 9 7 5 +``` + +思路:二叉树翻转 , 把二叉树上的每一个节点的左右子节点进行交换 + +``` +// 将整棵树的节点翻转 +TreeNode invertTree(TreeNode root) { + // base case + if (root == null) { + return null; + } + + /**** 前序遍历位置 ****/ + // root 节点需要交换它的左右子节点 + TreeNode tmp = root.left; + root.left = root.right; + root.right = tmp; + + // 让左右子节点继续翻转它们的子节点 + invertTree(root.left); + invertTree(root.right); + + return root; +} +``` + + +## 把二元查找树转变成排序的双向链表 + +难度:中等 +输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表。要求`不能创建任何新的结点`,只调整指针的指向。 + +``` + 10 + / \ + 6 14 +/\ / \ +4 8 12 16 +``` + +转换成双向链表 `4=6=8=10=12=14=16` + + +分析:题目要求不能创建任何节点,也就是只能调整各个节点的指向 +思路一: + + + + + +## 二叉树两个结点的最低共同父结点 (最近公共祖先) + +设计一个算法,找出二叉树上任意两个结点的最近共同父结点。复杂度如果是O(n2)则不得分 + + +输入二叉树中的两个结点,输出这两个结点在数中最低的共同父结点。 + +分析:求数中两个结点的最低共同结点是面试中经常出现的一个问题。这个问题至少有两个变种。 + + +``` +TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { + // base case + if (root == null) return null; + if (root == p || root == q) return root; + + TreeNode left = lowestCommonAncestor(root.left, p, q); + TreeNode right = lowestCommonAncestor(root.right, p, q); + // 情况 1 :p, q 在 root 为根的树中 + if (left != null && right != null) { + return root; + } + // 情况 2 :p, q 不在 在 root 为根的树中 + if (left == null && right == null) { + return null; + } + // 情况 3 : p, q 只有1个在root 为根的树中 + return left == null ? right : left; + } +``` + + + + +## 求一个二叉树中任意两个节点间的最大距离 + +两个节点的距离的定义是 这两个节点间边的个数,比如某个孩子节点和父节点间的距离是1,和相邻兄弟节点间的距离是2,优化时间空间复杂度。 + + + + + +## 在二元树中找出和为某一值的所有路径 + +题目:输入一个整数和一棵二元树。从树的根结点开始往下访问一直到叶结点所经过的所有结点形成一条路径。打印出和与输入整数相等的所有路径。 + +例如 输入整数22和如下二元树 +``` + 10 + / \ + 5 12 + /\ + 4 7 +``` +则打印出两条路径:`10, 12` 和 `10, 5, 7` 。 + + +1. 累加当前节点,累加和大于给定值 +2. 不为叶节点,左子树入栈 + + + +## 一棵排序二叉树,令 f=(最大值+最小值)/2,设计一个算法,找出距离f值最近、大于f值的结点。 +复杂度如果是O(n2)则不得分。 + + + + diff --git "a/9 Algorithms Job Interview/7.1 \344\272\214\345\217\211\346\240\221-\351\201\215\345\216\206.md" "b/9 Algorithms Job Interview/7.1 \344\272\214\345\217\211\346\240\221-\351\201\215\345\216\206.md" new file mode 100644 index 0000000..d4d513d --- /dev/null +++ "b/9 Algorithms Job Interview/7.1 \344\272\214\345\217\211\346\240\221-\351\201\215\345\216\206.md" @@ -0,0 +1,97 @@ +# 二叉树-遍历 + + +> 快速排序就是个二叉树的前序遍历,归并排序就是个二叉树的后序遍历 + + +## 2种方法实现二叉树的前序遍历。 + +递归和非递归 + + + + +## 按层打印二叉树 + +输入一颗二元树,从上往下按层打印树的每个结点,同一层中按照从左往右的顺序打印。 +例如输入 + +``` + 8 + /\ + 6 10 +/ \ /\ +5 7 9 11 +``` + +输出`8 6 10 5 7 9 11` + + + + +## 二元树的深度 + +题目:输入一棵二元树的根结点,求该树的深度.从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。 +例如:输入二元树: +``` + + 10 + / \ + 6 14 + / / \ + 4 12 16 +``` + +输出该树的深度3。 + + + +分析:这道题本质上还是考查二元树的遍历。对于一颗完全二叉树,要求给所有节点加上一个pNext指针,指向同一层的相邻节点;如果当前节点已经是该层的最后一个节点,则将pNext指针指向NULL;给出程序实现,并分析时间复杂度和空间复杂度。 + + +## 二元树的最小深度 + + +例如:输入二元树: +``` + + 10 + / \ + 6 14 + / \ + 12 16 +``` + +输出该树的最小深度2。 + + + +## 完全二叉树的节点个数 + + + + + + +## 判断整数序列是不是二元查找树的后序遍历结果 + +题目:输入一个整数数组,判断该数组是不是某二元查找树的后序遍历的结果。 +如果是返回true,否则返回false。 + +例如输入`5、7、6、9、11、10、8`,由于这一整数序列是如下树的后序遍历结果: +``` + 8 + / \ + 6 10 + /\ / \ + 5 7 9 11 +``` +因此返回true。如果输入7、4、6、5,没有哪棵树的后序遍历的结果是这个序列,因此返回false。 + + + + + + + + diff --git "a/9 Algorithms Job Interview/7.2 \344\272\214\345\217\211\346\240\221-\345\273\272\346\240\221.md" "b/9 Algorithms Job Interview/7.2 \344\272\214\345\217\211\346\240\221-\345\273\272\346\240\221.md" new file mode 100644 index 0000000..905810c --- /dev/null +++ "b/9 Algorithms Job Interview/7.2 \344\272\214\345\217\211\346\240\221-\345\273\272\346\240\221.md" @@ -0,0 +1,19 @@ +# 二叉树-建树 + + +## 把一个有序整数数组放到二叉树中? + +分析:本题考察二叉搜索树的建树方法,简单的递归结构。 + +关于树的算法设计一定要联想到递归,因为树本身就是递归的定义。而,学会把递归改称非递归也是一种必要的技术。 +毕竟,递归会造成栈溢出,关于系统底层的程序中不到非不得以最好不要用。但是对某些数学问题,就一定要学会用递归去解决。 + + + + +## 恢复树结构 + +有一棵树(树上结点为字符串或者整数),请写代码将树的结构和数据写到一个文件中,并能通过读取该文件恢复树结构。 + + + diff --git "a/9 Algorithms Job Interview/7.5 \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" "b/9 Algorithms Job Interview/7.5 \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" new file mode 100644 index 0000000..31b323c --- /dev/null +++ "b/9 Algorithms Job Interview/7.5 \344\272\214\345\217\211\346\220\234\347\264\242\346\240\221.md" @@ -0,0 +1,27 @@ +# 二叉搜索树 + +关于二叉查找树特性[参考这里](../4%20Tree/2-二叉查找树/二叉查找树.md) + +## 验证二叉搜索树 + + +## 二叉搜索树中的节点搜索 + + +## 二叉搜索树中的插入操作 + + +## 删除二叉搜索树中的节点 + + + + + + + + + + + + + diff --git "a/9 Algorithms Job Interview/7.9 \346\240\221.md" "b/9 Algorithms Job Interview/7.9 \346\240\221.md" new file mode 100644 index 0000000..9165b72 --- /dev/null +++ "b/9 Algorithms Job Interview/7.9 \346\240\221.md" @@ -0,0 +1,14 @@ +# 树 + + +## 给一棵树 求最大宽度 + + + + +## 树的广度遍历深度遍历 + + + + +## 多叉树的层序遍历 diff --git "a/9 Algorithms Job Interview/8 \345\233\276.md" "b/9 Algorithms Job Interview/8 \345\233\276.md" new file mode 100644 index 0000000..7bce7e4 --- /dev/null +++ "b/9 Algorithms Job Interview/8 \345\233\276.md" @@ -0,0 +1,33 @@ +# 图 + +深度优先遍历(DFS)和广度优先遍历(BFS) 深度优先遍历(Depth First Search, 简称 DFS) 与广度优先遍历(Breath First Search)是图论中两种非常重要的算法,生产上广泛用于拓扑排序,寻路(走迷宫),搜索引擎,爬虫等,也频繁出现在 leetcode,高频面试题中 + + +## 深度优先遍历 + +深度优先遍历(DFS) + + +## 广度优先遍历 + + +广度优先遍历(BFS) + + + +## 华为面试题:一类似于蜂窝的结构的图,进行搜索最短路径(要求5分钟) + + + +## 求一个有向连通图的割点,割点的定义是,如果除去此节点和与其相关的边, +有向图不再连通,描述算法。 + + + + +## 平面上N个点,每两个点都确定一条直线, +求出斜率最大的那条直线所通过的两个点(斜率不存在的情况不考虑)。时间效率越高越好。 + + + + diff --git "a/Algorithms Job Interview/\346\231\272\345\212\233\346\200\235\347\273\264\350\256\255\347\273\203.md" "b/9 Algorithms Job Interview/9 \346\231\272\345\212\233\346\200\235\347\273\264\350\256\255\347\273\203.md" similarity index 98% rename from "Algorithms Job Interview/\346\231\272\345\212\233\346\200\235\347\273\264\350\256\255\347\273\203.md" rename to "9 Algorithms Job Interview/9 \346\231\272\345\212\233\346\200\235\347\273\264\350\256\255\347\273\203.md" index 24dabd9..0a48180 100644 --- "a/Algorithms Job Interview/\346\231\272\345\212\233\346\200\235\347\273\264\350\256\255\347\273\203.md" +++ "b/9 Algorithms Job Interview/9 \346\231\272\345\212\233\346\200\235\347\273\264\350\256\255\347\273\203.md" @@ -1,3 +1,4 @@ +# 智力题 这部分内容的题目侧重思维发散。 @@ -200,10 +201,15 @@ A说不知道,B说不知道,C说不知道,然后A说知道了。 得:a=(1024/3125)X; 要使a为整数,X最小取3125. 减去加上的4个,所以,这堆桃子最少有3121个。 + 已知有个rand7()的函数,返回1到7随机自然数,让利用这个rand7()构造rand10() 随机1~10。 (参考答案:这题主要考的是对概率的理解。程序关键是要算出rand10,1到10,十个数字出现的考虑都为10%.根据排列组合,连续算两次rand7出现的组合数是7*7=49,这49种组合每一种出现考虑是相同的。怎么从49平均概率的转换为1到10呢?方法是: 1.rand7执行两次,出来的数为a1=rand7()-1,a2=rand7()-1. -2.如果a1*7+a2<40,b=(a1*7+a2)/4+1;如果a1*7+a2>=40,重复第一步。参考代码如下所示: +2.如果`a1*7+a2<40,b=(a1*7+a2)/4+1`;如果`a1*7+a2>=40`, 重复第一步。参考代码如下所示: + + +``` + int rand7() { return rand()%7+1; @@ -220,8 +226,7 @@ int rand10() } while (a10>= 40); return (a71*7+a72)/4+1; } - - +``` diff --git "a/Algorithms Job Interview/\347\263\273\347\273\237\350\256\276\350\256\241.md" "b/9 Algorithms Job Interview/91 \347\263\273\347\273\237\350\256\276\350\256\241.md" similarity index 55% rename from "Algorithms Job Interview/\347\263\273\347\273\237\350\256\276\350\256\241.md" rename to "9 Algorithms Job Interview/91 \347\263\273\347\273\237\350\256\276\350\256\241.md" index 84e8475..689718c 100644 --- "a/Algorithms Job Interview/\347\263\273\347\273\237\350\256\276\350\256\241.md" +++ "b/9 Algorithms Job Interview/91 \347\263\273\347\273\237\350\256\276\350\256\241.md" @@ -1,28 +1,27 @@ +# 系统设计 +## 要求设计一个DNS的Cache结构,要求能够满足每秒5000以上的查询,满足IP数据的快速插入,查询的速度要快。 -#### 要求设计一个DNS的Cache结构,要求能够满足每秒5000以上的查询,满足IP数据的快速插入,查询的速度要快。 - - -#### 设计一个系统处理词语搭配问题 +## 设计一个系统处理词语搭配问题 设计一个系统处理词语搭配问题,比如说 中国 和人民可以搭配, 则中国人民 人民中国都有效。要求: -*系统每秒的查询数量可能上千次; -*词语的数量级为10W; -*每个词至多可以与1W个词搭配 +* 系统每秒的查询数量可能上千次; +* 词语的数量级为10W; +* 每个词至多可以与1W个词搭配 当用户输入中国人民的时候,要求返回与这个搭配词组相关的信息。 -#### 任务调度 +## 任务调度 系统有很多任务,任务之间有依赖,比如B依赖于A,则A执行完后B才能执行 - (1)不考虑系统并行性,设计一个函数(Task *Ptask,int Task_num)不考虑并行度,最快的方法完成所有任务。 + (1)不考虑系统并行性,设计一个函数`(Task *Ptask,int Task_num)`不考虑并行度,最快的方法完成所有任务。 (2)考虑并行度,怎么设计 ``` @@ -41,14 +40,13 @@ ``` -#### 设计一种内存管理算法 +## 设计一种内存管理算法 相关的问题: -##### 请编写实现malloc()内存分配函数功能一样的代码 - -##### 用C语言实现函数memmove()函数 +* 请编写实现malloc()内存分配函数功能一样的代码 +* 用C语言实现函数memmove()函数 ``` void * memmove(void *dest, const void *src, size_t n) @@ -61,11 +59,11 @@ memmove()函数的功能是拷贝src所指的内存内容前n个字节到dest所 -#### A向B发邮件,B收到后读取并发送收到,但是中间可能丢失了该邮件,怎么设计一种最节省的方法,来处理丢失问题。 +## A向B发邮件,B收到后读取并发送收到,但是中间可能丢失了该邮件,怎么设计一种最节省的方法,来处理丢失问题。 -#### 设计一种算法求出算法复杂度 +## 设计一种算法求出算法复杂度 diff --git "a/9 Algorithms Job Interview/97 \345\205\266\344\273\226.md" "b/9 Algorithms Job Interview/97 \345\205\266\344\273\226.md" new file mode 100644 index 0000000..ff0d20f --- /dev/null +++ "b/9 Algorithms Job Interview/97 \345\205\266\344\273\226.md" @@ -0,0 +1,61 @@ +# 其他 + + + +## 两个圆相交 + +两个圆相交,交点是A1,A2。现在过A1点做一直线与两个圆分别相交另外一点B1,B2。B1B2可以绕着A1点旋转。问在什么情况下,B1B2最长 + + + + +## 输入四个点的坐标,求证四个点是不是一个矩形 + +关键点: +1.相邻两边斜率之积等于-1, +2.矩形边与坐标系平行的情况下,斜率无穷大不能用积判断。 +3.输入四点可能不按顺序,需要对四点排序。 + + + + +## 排列组合问题 + + +## 1,2,3,4,5 五个不同的数字,打印不同的排列。这就是一个无向图的遍历,把每个数字看成一个节点。 + + +## 用1、2、2、3、4、5这六个数字,写一个main函数,打印出所有不同的排列, + +如:512234、412345 等,要求:"4"不能在第三位,"3"与"5"不能相连. + +这是对上一题增加难度。但是需要 +1. 去掉 3,5之间的联通 +2. 2重复,过滤重复结果 treeset +3. 4不能在3位 + + + + + + +## 圆形和正方形是否相交 + +用最简单, 最快速的方法计算出下面这个圆形是否和正方形相交 +3D坐标系 原点(0.0,0.0,0.0) +圆形: +半径r = 3.0 +圆心o = (*.*, 0.0, *.*) + +正方形: +4个角坐标; +1:(*.*, 0.0, *.*) +2:(*.*, 0.0, *.*) +3:(*.*, 0.0, *.*) +4:(*.*, 0.0, *.*) + +分析: + +2个形状不相交: + + diff --git a/9 Algorithms Job Interview/README.md b/9 Algorithms Job Interview/README.md new file mode 100644 index 0000000..00f0f1d --- /dev/null +++ b/9 Algorithms Job Interview/README.md @@ -0,0 +1,359 @@ +# 面试题 + +这部分内容是算法问题合集,题目大多来自网络和书籍。我做了下简单的整理,很多题做了一些思路标记。 + +## 小结 + +面试中如何更好的写算法题 + +* ide 工具 & 文本框 ;文本框环境写算法是通常没有智能提示,有些基础数据结构可能要自己定义 +* 语言基础语法:各常用集合的api,要能够默写 +* 题目:熟悉各种题目,避免在题目理解上花太多时间跟面试官拉齐 +* 编程思路和框架:拿到题目以后能快速套思路和代码框架 +* 编程细节:多练习,总结 + + +## 常用解题套路工具 + +* 数组,字符串问题:二分查找、 快慢指针、 左右指针、 滑动窗口、 前缀和数组、 差分数组。 +* 二叉树问题:递归 +* [动态规划问题](../8%20Algorithms%20Analysis/动态规划.md) +* [常见算法题目Java实现](https://github.com/nonstriater/deep-in-java/tree/master/src/main/java/com/nonstriater/deepinjava/algo) + + +## 刷题框架套路 + + +### 遍历 + +``` +# 数组遍历框架 +void traverse(int[] arr) { + for (int i = 0; i < arr.length; i++) { + // 迭代访问 arr[i] + } +} + +# 链表遍历框架 +void traverse(ListNode head) { + + ListNode p = head; + while(p.next != null) + ... + p = p.next + +} +``` + + + +### 递归 + +```Java +# 链表递归 +void traverse(ListNode head) { + // 递归访问 head.val + traverse(head.next); +} + + +# 二叉树递归 +void traverse(TreeNode root) { + traverse(root.left); + traverse(root.right); +} + +# 多叉树递归 +void traverse(TreeNode root) { + for (TreeNode child : root.children) + traverse(child); +} + +# 图的递归:,用个布尔数组 visited 做标记就行了 + +``` + +比如,二叉树的最近公共祖先 + +```Java +TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { + // base case + if (root == null) return null; + if (root == p || root == q) return root; + + TreeNode left = lowestCommonAncestor(root.left, p, q); + TreeNode right = lowestCommonAncestor(root.right, p, q); + // 情况 1 :p, q 在 root 为根的树中 + if (left != null && right != null) { + return root; + } + // 情况 2 :p, q 不在 在 root 为根的树中 + if (left == null && right == null) { + return null; + } + // 情况 3 : p, q 只有1个在root 为根的树中 + return left == null ? right : left; + } +``` + + +### 二分查找 + +[查找算法](../7%20Search/README.md) + +```Java +// 1, 2, 2, 2, 2, 3 +public int bsearch(int[] nums, int target){ + + if(nums == null) { + return -1; + } + + int low = 0; + int high = nums.length;// 注意 + + while(low < high){ + middle = (high + low ) / 2; + if(nums[middle] < target){ + low = middle + 1; + } else if (nums[middle] == target) { + high = middle;//继续向左早 + } else { + high = middle; // 注意 , 这里没有 -1 + } + } + + return low; + } +``` + + +### 快慢指针 + +如下判断链表是否有环 + +``` +public static boolean hasCycle(LinkNode head) { + if(head == null) { + return false; + } + + LinkNode p = head, q = head; + while(p.next != null && p.next.next != null && q.next != null) { + p = p.next.next; + q = q.next; + if(p == q) { + return true; + } + } + return false; + } +``` + + +### 左右指针 + + +快排中 挖坑(pivot) 排序 +```Java +// pivot 选择 尾部节点, 代码写起来更加简单; 移动元素更方便 +// 左右指针技巧 +static int partition(int[] nums, int left, int right){ + + int pivot = nums[right];//选尾部节点作为 pivot + int end = right; + + right--; + while (left < right) { + + if (nums[left] <= pivot) { + left ++ ; //左边指针 窗口变小 + continue; + } + + //元素比 pivot 大,右边指针 窗口变小 + //swap left & right + swap(nums, left, right); + right--; + + } + + // 跟 pivot 元素置换 + int i = 0; + if (nums[left] <= pivot) { + //swap left+1 & pivot + i = left +1; + } else {//swap left & pivot + i = left ; + } + + swap(nums, i, end); + + return i; + } +``` + + +### 滑动窗口 + +双指针中有一类比较难的技巧就是`滑动窗口` + +滑动窗口: 无重复字符的最长子串 + +```Java +//如 ”abcabbcbb“ 输出 3 +public static int longestSubString(char[] s){ + int left = 0, right = 0; + int res = 0; // 记录最长结果 + + Map 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 #include - #define PrintInt(expr) printf("%s : %d\n",#expr,(expr)) + +#define PrintInt(expr) printf("%s : %d\n",#expr,(expr)) + int main() { int y = 100; diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/21.c" b/9 Algorithms Job Interview/codes/21.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/21.c" rename to 9 Algorithms Job Interview/codes/21.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/22.c" b/9 Algorithms Job Interview/codes/22.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/22.c" rename to 9 Algorithms Job Interview/codes/22.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/23.c" b/9 Algorithms Job Interview/codes/23.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/23.c" rename to 9 Algorithms Job Interview/codes/23.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/24.c" b/9 Algorithms Job Interview/codes/24.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/24.c" rename to 9 Algorithms Job Interview/codes/24.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/25.c" b/9 Algorithms Job Interview/codes/25.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/25.c" rename to 9 Algorithms Job Interview/codes/25.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/26.c" b/9 Algorithms Job Interview/codes/26.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/26.c" rename to 9 Algorithms Job Interview/codes/26.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/27.c" b/9 Algorithms Job Interview/codes/27.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/27.c" rename to 9 Algorithms Job Interview/codes/27.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/28.c" b/9 Algorithms Job Interview/codes/28.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/28.c" rename to 9 Algorithms Job Interview/codes/28.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/Power.c" b/9 Algorithms Job Interview/codes/4 numer/Power.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/Power.c" rename to 9 Algorithms Job Interview/codes/4 numer/Power.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/3.c" b/9 Algorithms Job Interview/codes/4 numer/integer_to_bin.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/3.c" rename to 9 Algorithms Job Interview/codes/4 numer/integer_to_bin.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/9.c" b/9 Algorithms Job Interview/codes/4 numer/isSquare.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/9.c" rename to 9 Algorithms Job Interview/codes/4 numer/isSquare.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/one_appear_count_by_binary.c" b/9 Algorithms Job Interview/codes/4 numer/one_appear_count_by_binary.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/one_appear_count_by_binary.c" rename to 9 Algorithms Job Interview/codes/4 numer/one_appear_count_by_binary.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/1.c" b/9 Algorithms Job Interview/codes/4 numer/string_to_integer.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/1.c" rename to 9 Algorithms Job Interview/codes/4 numer/string_to_integer.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/4-1.c" b/9 Algorithms Job Interview/codes/4-1.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/4-1.c" rename to 9 Algorithms Job Interview/codes/4-1.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/delete_occurence_character.c" b/9 Algorithms Job Interview/codes/5 array/delete_occurence_character.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/delete_occurence_character.c" rename to 9 Algorithms Job Interview/codes/5 array/delete_occurence_character.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/2.c" b/9 Algorithms Job Interview/codes/5 array/factorial.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/2.c" rename to 9 Algorithms Job Interview/codes/5 array/factorial.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/fibonacci.c" b/9 Algorithms Job Interview/codes/5 array/fibonacci.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/fibonacci.c" rename to 9 Algorithms Job Interview/codes/5 array/fibonacci.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/longest_continuious_number.c" b/9 Algorithms Job Interview/codes/5 array/longest_continuious_number.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/longest_continuious_number.c" rename to 9 Algorithms Job Interview/codes/5 array/longest_continuious_number.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/11.c" b/9 Algorithms Job Interview/codes/5 array/print_continuous_sequence_sum.c similarity index 94% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/11.c" rename to 9 Algorithms Job Interview/codes/5 array/print_continuous_sequence_sum.c index ee5ac8b..b6667a2 100644 --- "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/11.c" +++ b/9 Algorithms Job Interview/codes/5 array/print_continuous_sequence_sum.c @@ -2,6 +2,7 @@ #include "stdio.h" +//打印和为n的2个值 void print_continuous_sequence_sum(int n){// n=1,2 int small=1; diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/5.c" b/9 Algorithms Job Interview/codes/5.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/5.c" rename to 9 Algorithms Job Interview/codes/5.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/print_matrix.c" b/9 Algorithms Job Interview/codes/6 matrix/print_matrix.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/print_matrix.c" rename to 9 Algorithms Job Interview/codes/6 matrix/print_matrix.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/6.c" b/9 Algorithms Job Interview/codes/7 bianrytree/binary_search.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/6.c" rename to 9 Algorithms Job Interview/codes/7 bianrytree/binary_search.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/bt1.c" b/9 Algorithms Job Interview/codes/7 bianrytree/bt1.c similarity index 71% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/bt1.c" rename to 9 Algorithms Job Interview/codes/7 bianrytree/bt1.c index 8816c9e..7e72477 100644 --- "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/bt1.c" +++ b/9 Algorithms Job Interview/codes/7 bianrytree/bt1.c @@ -3,11 +3,9 @@ 二叉查找树变成双向链表 */ -strcut BSTreeNode{ - +typedef struct BSTreeNode{ int m_nValue; - BSTreeNode *m_pLeft,m_pRight; - + struct BSTreeNode *m_pLeft, *m_pRight; }BSTree; void convertDoubleLinks(BSTree *root){ diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/7.c" b/9 Algorithms Job Interview/codes/7.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/7.c" rename to 9 Algorithms Job Interview/codes/7.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/8.c" b/9 Algorithms Job Interview/codes/8.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/8.c" rename to 9 Algorithms Job Interview/codes/8.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c1.c" b/9 Algorithms Job Interview/codes/c1.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c1.c" rename to 9 Algorithms Job Interview/codes/c1.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c10.c" b/9 Algorithms Job Interview/codes/c10.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c10.c" rename to 9 Algorithms Job Interview/codes/c10.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c11-2.c" b/9 Algorithms Job Interview/codes/c11-2.c similarity index 90% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c11-2.c" rename to 9 Algorithms Job Interview/codes/c11-2.c index fda7caf..e5c3e4b 100644 --- "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c11-2.c" +++ b/9 Algorithms Job Interview/codes/c11-2.c @@ -12,7 +12,7 @@ int main() { char ch = 'a'; - foobar1(33, ch); + foobar1(); foobar2(); return 0; } diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c11.c" b/9 Algorithms Job Interview/codes/c11.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c11.c" rename to 9 Algorithms Job Interview/codes/c11.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c12.c" b/9 Algorithms Job Interview/codes/c12.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c12.c" rename to 9 Algorithms Job Interview/codes/c12.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c13-1.c" b/9 Algorithms Job Interview/codes/c13-1.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c13-1.c" rename to 9 Algorithms Job Interview/codes/c13-1.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c13-2.c" b/9 Algorithms Job Interview/codes/c13-2.c similarity index 85% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c13-2.c" rename to 9 Algorithms Job Interview/codes/c13-2.c index 7b8ea97..cbb3ff5 100644 --- "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c13-2.c" +++ b/9 Algorithms Job Interview/codes/c13-2.c @@ -1,9 +1,8 @@ #include "c13-1.c" - extern int *arr; + int main() { - arr[1] = 100; return 0; } diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c14.c" b/9 Algorithms Job Interview/codes/c14.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c14.c" rename to 9 Algorithms Job Interview/codes/c14.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c15.c" b/9 Algorithms Job Interview/codes/c15.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c15.c" rename to 9 Algorithms Job Interview/codes/c15.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c16.c" b/9 Algorithms Job Interview/codes/c16.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c16.c" rename to 9 Algorithms Job Interview/codes/c16.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c17.c" b/9 Algorithms Job Interview/codes/c17.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c17.c" rename to 9 Algorithms Job Interview/codes/c17.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c18.c" b/9 Algorithms Job Interview/codes/c18.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c18.c" rename to 9 Algorithms Job Interview/codes/c18.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c19.c" b/9 Algorithms Job Interview/codes/c19.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c19.c" rename to 9 Algorithms Job Interview/codes/c19.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c2.c" b/9 Algorithms Job Interview/codes/c2.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c2.c" rename to 9 Algorithms Job Interview/codes/c2.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c20.c" b/9 Algorithms Job Interview/codes/c20.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c20.c" rename to 9 Algorithms Job Interview/codes/c20.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c3.c" b/9 Algorithms Job Interview/codes/c3.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c3.c" rename to 9 Algorithms Job Interview/codes/c3.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c4.c" b/9 Algorithms Job Interview/codes/c4.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c4.c" rename to 9 Algorithms Job Interview/codes/c4.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c5.c" b/9 Algorithms Job Interview/codes/c5.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c5.c" rename to 9 Algorithms Job Interview/codes/c5.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c6.c" b/9 Algorithms Job Interview/codes/c6.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c6.c" rename to 9 Algorithms Job Interview/codes/c6.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c7.c" b/9 Algorithms Job Interview/codes/c7.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c7.c" rename to 9 Algorithms Job Interview/codes/c7.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c8.c" b/9 Algorithms Job Interview/codes/c8.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c8.c" rename to 9 Algorithms Job Interview/codes/c8.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c9.c" b/9 Algorithms Job Interview/codes/c9.c similarity index 100% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/c9.c" rename to 9 Algorithms Job Interview/codes/c9.c diff --git "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/most_visit_ip.c" b/9 Algorithms Job Interview/codes/most_visit_ip.c similarity index 99% rename from "Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/most_visit_ip.c" rename to 9 Algorithms Job Interview/codes/most_visit_ip.c index 5c64dca..8ca03fd 100644 --- "a/Algorithms Job Interview/\346\265\213\350\257\225\344\273\243\347\240\201/most_visit_ip.c" +++ b/9 Algorithms Job Interview/codes/most_visit_ip.c @@ -8,17 +8,12 @@ #define test_file_path "./ip.txt" #define ip_count 100000000 //随机1亿个IP #define tmp_file_count 32 - #define mem_count 128*1024*1028 //128MB个IP空间 - int hash(unsigned i){ - return i>>27; - } - int main(){ @@ -52,9 +47,6 @@ int main(){ } } - - - //开始读测试数据IP,按IP,映射到32个文件中 FILE *testfd = fopen(test_file_path2,"r"); if (!testfd) @@ -76,8 +68,6 @@ int main(){ } - - // 依次读入每个文件并统计, hash_map 统计每个区间段的最大IP int hash_map[mem_count]; int max_ip; diff --git "a/Algorithms Job Interview/\345\211\221\346\214\207offer/README.md" "b/9 Algorithms Job Interview/\345\211\221\346\214\207offer/README.md" similarity index 92% rename from "Algorithms Job Interview/\345\211\221\346\214\207offer/README.md" rename to "9 Algorithms Job Interview/\345\211\221\346\214\207offer/README.md" index dcee0d5..f4920c6 100644 --- "a/Algorithms Job Interview/\345\211\221\346\214\207offer/README.md" +++ "b/9 Algorithms Job Interview/\345\211\221\346\214\207offer/README.md" @@ -1,3 +1,4 @@ +# 《剑指offer》 《剑指offer》 这本书给出了50到面试题,涉及到字符串处理,堆栈,链表,二叉树等问题的处理。 @@ -7,273 +8,209 @@ * 本书完整源代码在: -### 赋值运算符函数 -### 实现Singleton模式 +# 数值 -### 二维数组中的查找 +### 二进制中1的个数 -二维数组中每一行从左到右递增,每一列从上到下递增,判断数组中是否包含该整数。 +输入一个整数,输出该数二进制中1出现的次数。比如9的二进制 10001,输出是2 + +`n=n&n-1` ``` -bool find(int *matrix,int rows,int columns,int numbers) +int one_appear_count(int n) ``` +### 数值的整数次方 -### 替换空格 - -如把字符串中的每个空格替换成`%20` - -`二遍扫描` +要求不得使用库函数。这里注意考虑指数是0和负数的情况 ``` -void replace_blank(char *str); +double power(double base,int exponent) ``` -### 从尾到头打印链表 +### 打印1到最大的n位数 -`栈` +比如n=3,就打印1到999 ``` -void print_reversing(LinkList *head) +void print_to_max_with_length(int n) ``` -### 重建二叉树 +### 求 1+2+...+n + +要求不用乘除法,for/while/if/else/switch等关键字及条件判断语句 -输入某二叉树的前序遍历和中序遍历的结果,重建该二叉树 ``` -BinaryTree *construct(int *preorder,int inroder,int length); +long long sum(unsigned int n); ``` +### 不用加减乘除做加法 -### 用两个栈实现队列 - -队列就是在尾部插入节点,头部删除节点。 - +求2个整数之和 -### 旋转数组的最小数字 +`位运算` -旋转数组是指把一个数组最开始的若干个元素搬到数组的末尾。输入一个递增排序的数组的旋转,比如{3,4,5,1,2}是{1,2,3,4,5}的一个旋转。求该数组的最小值。 ``` -int min(int *num, int length) +int sum(int a,int b ``` -### 菲波那切数列 - -``` -long long fabonacci(unsigned n) -``` -### 二进制中1的个数 +### 丑数 -输入一个整数,输出该数二进制中1出现的次数。比如9的二进制 10001,输出是2 +> 只包含因子 2,3,5的数叫做丑数;比如 6(2x3), 8(2x2x2) 是丑数(ugly number) -`n=n&n-1` +求按从小到大的顺序,第1500个丑数 ``` -int one_appear_count(int n) +int ugly(int n) ``` -### 数值的整数次方 -要求不得使用库函数。这里注意考虑指数是0和负数的情况 -``` -double power(double base,int exponent) -``` +# 字符串 -### 打印1到最大的n位数 - -比如n=3,就打印1到999 - -``` -void print_to_max_with_length(int n) -``` -### 在O(1)时间删除链表节点 +### 替换空格 -已经有一个头节点指针,还有一个指向改删除节点的指针 +如把字符串中的每个空格替换成`%20` -`用下一个节点的内容覆盖当前删除节点的内容,删除下一个节点` +`二遍扫描` ``` -void deleteNode(LinkList *head,LinkList *targetToDelete); +void replace_blank(char *str); ``` -### 调整数组顺序使奇数位于偶数前面 - -调整后,所有奇数在前半部分,偶数在后半部分 - -`两边向中间扫描` - +### 把字符串转换成整数 -``` -void reorder(int *data,int length) -``` +比如 "12343567754" -> 12343567754 -### 输出链表中倒数第K个节点 +`NULL,空串,正负号,溢出` -`使用两个指针,一个先走k-1步` ``` -void print_lastK(LinkList *head); +int strToInt(char str); ``` -### 反转链表 +### 第一个只出现一次的字符 -`三个指针` +在字符串中查找第一个只出现一次的字符 + +`哈希表:值为出现的次数` `二次扫描` ``` -void reverse(LinkList *head); +char find_appear_once_char(char *string) ``` -反转二叉树呢? - -### 合并2个排序的链表 +### 字符串的排列 -要求合并以后链表任然排序 +输入一个字符串,打印该字符串中字符的所有排列 -`递归` +`递归,分解` ``` -LinkList *merge(LinkList *one,LinkList *two); +void print_full_permutation(char *string) ``` -### 树的子结构 -考察二叉树的基本操作。输入2课二叉树A和B,判断B是不是A的子结构。 +### 反转单词顺序 VS 左旋转字符串 -``` -struct BinaryTreeNode{ - int m_value; - BinaryTreeNode *m_pleft; - BinaryTreeNode *m_pRight; -} -``` +a. 翻转句子中单词的顺序,但单词内字符不变。如 『I am a student』 -> 『student. a am I』 + +`先以单词为单位翻转,整个句子再次翻转` ``` - 8 - / \ 10 - / \ / \ - 6 10 子结构 11 9 - / \ / \ - 5 7 9 11 +char *reverse_by_word(char *string) ``` +b. 左旋转字符串是把字符串其那面的若干位转义到字符串的尾部。比如"abcedfsz"和数字2,结果是"cedfszab" + ``` -bool subTree(BinaryTreeNode *root1,BinaryTreeNode *root2); +char *left_rotate_string(char *s,int n) ``` -### 二叉树翻转 - -``` - 8 8 - / \ / \ - / \ / \ - 6 10 翻转后 10 6 - / \ / \ / \ / \ - 5 7 9 11 11 9 7 5 -``` - -`交换每个节点的左右子树` +# 链表 -``` -void reverse(BinaryTreeNode *root); -``` +### 从尾到头打印链表 -### 从外向里顺时针打印矩阵 +`栈` ``` -void print_matrix_clockwise(int *matrix,int cols,int rows); +void print_reversing(LinkList *head) ``` -延伸:按大小顺序打印矩阵 -### 实现一个能找到栈的最小元素的函数 +### 两个链表的第一个公共节点 + +`长的链表先走k步` -`最小元素用辅助栈保存` ``` -int min(Stack *stack) +LinkListNode *common_node(LinkList *head1,LinkList head2); ``` -### 栈的压入,弹出序列 -输入2个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。比如: +### 在O(1)时间删除链表节点 -1,2,3,4,5是压栈序列,4,5,3,2,1是弹栈序列,但是4,3,5,1,2就不是弹栈序列 +已经有一个头节点指针,还有一个指向改删除节点的指针 + +`用下一个节点的内容覆盖当前删除节点的内容,删除下一个节点` ``` -bool is_pop_order(int *push,int *pop,int length) +void deleteNode(LinkList *head,LinkList *targetToDelete); ``` -### 从上往下打印二叉树 +### 输出链表中倒数第K个节点 -`辅助队列` +`使用两个指针,一个先走k-1步` ``` -void print_binary_level(BinaryTreeNode *root) +void print_lastK(LinkList *head); ``` -### 二叉搜索树的后续遍历序列 +### 反转链表 -输入一个整数数组,判断该数组是不是某二叉查找树的后续遍历序列的结果。比如【 5,7,6,9,11,10,8】 是下面二叉查找树的后续遍历结果: +`三个指针` ``` - 8 - / \ - / \ - 6 10 - / \ / \ - 5 7 9 11 +void reverse(LinkList *head); ``` -`寻找规律` - -``` -bool is_post_order(BST *root,int *data, int length); -``` +反转二叉树呢? -### 二叉树中和为某一值的路径 -``` - 10 - / \ - / \ - 5 12 - / \ - 5 7 -``` +### 合并2个排序的链表 -和为22的路径有2条:10--5--7, 10--12 +要求合并以后链表任然排序 -`递归,栈` +`递归` ``` -void print_path(BinaryTree *root,int n) +LinkList *merge(LinkList *one,LinkList *two); ``` + ### 复杂链表的复制 在复杂链表结构中,每个节点都有一个指向下一个节点的m_pNext;还有一个指向任意节点的m_pSibling @@ -290,27 +227,11 @@ typedef struct LinkListNode{ LinkList * copy(LinkList *head); ``` -### 二叉搜索树与双向链表 - -将二叉搜索树转换成一个排序的双向链表,只调整树中节点的指针指向 -`递归` `分解问题` -``` -BST *transform(BST *root); -``` +# 列表&数列 -### 字符串的排列 - -输入一个字符串,打印该字符串中字符的所有排列 - -`递归,分解` - -``` -void print_full_permutation(char *string) -``` - ### 数组中出现次数超过一半的数字 @@ -362,52 +283,85 @@ int one_appear_count(int n) int minSort(int *nums, int length); ``` -### 丑数 -> 只包含因子 2,3,5的数叫做丑数 +### 菲波那切数列 -求按从小到大的顺序,第1500个丑数 +> 波那切数列 fabonacci , 也叫黄金分割数列, F (0)=0, F (1)=1, F (n)= F (n - 1)+ F (n - 2) ; 即 0、1、1、2、3、5、8、13、21、34、…… ``` -int ugly(int n) +long long fabonacci(unsigned n) ``` -### 第一个只出现一次的字符 +### 调整数组顺序使奇数位于偶数前面 -在字符串中查找第一个只出现一次的字符 +调整后,所有奇数在前半部分,偶数在后半部分 + +`两边向中间扫描` -`哈希表:值为出现的次数` `二次扫描` ``` -char find_appear_once_char(char *string) +void reorder(int *data,int length) ``` -### 数组中的逆序对 ->数组中的两个数字如果前面一个数字大于后面的数字,这两个数字组成一个逆序对。如:[7,5,6,4] 的逆序对:(7,5)(7,6)(7,4)(5,4)(6,4) +### 旋转数组的最小数字 -输入一个数组,求出这个数组逆序对总数。 +旋转数组是指把一个数组最开始的若干个元素搬到数组的末尾。输入一个递增排序的数组的旋转,比如{3,4,5,1,2}是{1,2,3,4,5}的一个旋转。求该数组的最小值。 -`归并排序 O(nlogn),空间O(n)` +``` +int min(int *num, int length) +``` + + + +### 数组中只出现一次的数字 + +数组中除了2个数字之外,其他的数组都出现了2次,找出这两个数 + +`异或` `二进制` + +>如果是只有1个数字只出现一次,我们可以通过对数组依次做异或运算。 + +如果我们能把原数组分成2个子数组,每个子数组都包含一个只出现一次的数字,问题就能解决了。我们把数组中的所有数字依次做异或操作,如果有2个数字不一样,结果肯定不是0,且异或结果数字的二进制表示中至少有一位是1(不然结果不就是0了) + +1. 在结果数字二进制表示中找到第一个为1的位的位置,标记n +2. 以二进制表示中第n位是不是1为标准,把原数组分成2个子数组 ``` -int reversePairs(int *data,int length) +void find_two_numbers_appear_once(int *data,int length,int *ouput) ``` -### 两个链表的第一个公共节点 +### 和为s的两个数字 VS 和为s的连续正数序列 -`长的链表先走k步` +有一个递增排序数组,和一个数字s,找出数组中的2个数,使得和等于s。输出任意一对即可 +`两边向中间扫描` ``` -LinkListNode *common_node(LinkList *head1,LinkList head2); +void print_two_numbers(int *data,int length,int sum) +``` + + + +### 数组中的逆序对 + +> 数组中的两个数字如果前面一个数字大于后面的数字,这两个数字组成一个逆序对。如:[7,5,6,4] 的逆序对:(7,5)(7,6)(7,4)(5,4)(6,4) + +输入一个数组,求出这个数组逆序对总数。 + +`归并排序 O(nlogn),空间O(n)` + + +``` +int reversePairs(int *data,int length) ``` + ### 数字在排序数组中出现的次数 比如 {1,2,3,3,3,3,4,5}, 数字 3出现了4次 @@ -419,131 +373,231 @@ int appear_count(int *nums,int length,int n); ``` -### 二叉树的深度 -`递归` +### n个色子的点数 + +把n个色子丢地上,朝上一面的点数之和为s。输入n,打印可能的值出现的概率 ``` -int tree_depth(BTree *root); +void print_sum_probability(int n) ``` -### 数组中只出现一次的数字 -数组中除了2个数字之外,其他的数组都出现了2次,找出这两个数 +### 扑克牌中的顺子 -`异或` `二进制` +从扑克牌从随机抽5张牌,判断是不是顺子。A是1,J~K是11~13,大小王可以看出任意数字。 ->如果是只有1个数字只出现一次,我们可以通过对数组依次做异或运算。 -如果我们能把原数组分成2个子数组,每个子数组都包含一个只出现一次的数字,问题就能解决了。我们把数组中的所有数字依次做异或操作,如果有2个数字不一样,结果肯定不是0,且异或结果数字的二进制表示中至少有一位是1(不然结果不就是0了) +``` +bool is_straight(int *data,int length) +``` -1. 在结果数字二进制表示中找到第一个为1的位的位置,标记n -2. 以二进制表示中第n位是不是1为标准,把原数组分成2个子数组 +### 圆圈中最后剩下的数字(约瑟夫问题) + +> 约瑟夫问题: 又称为约瑟夫环, N个人围成一圈,从第一个开始报数,第M个将被杀掉,最后剩下一个,其余人都将被杀掉。例如N=6,M=5,被杀掉的顺序是:5,4,6,2,3 + +0,1,...,n-1 这n个数字排成一个圆圈,从数字0开始从这个圆圈里面删除第m个数字,求出这个圆圈里最后剩下的数字。 ``` -void find_two_numbers_appear_once(int *data,int length,int *ouput) +int last_remaining(unsigned int n,unsigned int m) ``` -### 和为s的两个数字 VS 和为s的连续正数序列 +# 栈 & 队列 -有一个递增排序数组,和一个数字s,找出数组中的2个数,使得和等于s。输出任意一对即可 -`两边向中间扫描` +### 用两个栈实现队列 + +队列就是在尾部插入节点,头部删除节点。 + +### 实现一个能找到栈的最小元素的函数 + +`最小元素用辅助栈保存` + ``` -void print_two_numbers(int *data,int length,int sum) +int min(Stack *stack) ``` -### 反转单词顺序 VS 左旋转字符串 -a. 翻转句子中单词的顺序,但单词内字符不变。如 『I am a student』 -> 『student. a am I』 +### 栈的压入,弹出序列 -`先以单词为单位翻转,整个句子再次翻转` +输入2个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。比如: +1,2,3,4,5是压栈序列,4,5,3,2,1是弹栈序列,但是4,3,5,1,2就不是弹栈序列 ``` -char *reverse_by_word(char *string) +bool is_pop_order(int *push,int *pop,int length) ``` -b. 左旋转字符串是把字符串其那面的若干位转义到字符串的尾部。比如"abcedfsz"和数字2,结果是"cedfszab" + + + +# 矩阵 + +> 矩阵用二维数组表示 + + + +### 从外向里顺时针打印矩阵 ``` -char *left_rotate_string(char *s,int n) +void print_matrix_clockwise(int *matrix,int cols,int rows); ``` +延伸:按大小顺序打印矩阵 -### n个色子的点数 -把n个色子丢地上,朝上一面的点数之和为s。输入n,打印可能的值出现的概率 +### 二维数组中的查找 + +二维数组中每一行从左到右递增,每一列从上到下递增,判断数组中是否包含该整数。 + ``` -void print_sum_probability(int n) +bool find(int *matrix,int rows,int columns,int numbers) ``` -### 扑克牌中的顺子 -从扑克牌从随机抽5张牌,判断是不是顺子。A是1,J~K是11~13,大小王可以看出任意数字。 + +# 二叉树 + +### 重建二叉树 + +输入某二叉树的前序遍历和中序遍历的结果,重建该二叉树 + ``` -bool is_straight(int *data,int length) +BinaryTree *construct(int *preorder,int inroder,int length); ``` -### 圆圈中最后剩下的数字(约瑟夫问题) -0,1,...,n-1 这n个数字排成一个圆圈,从数字0开始从这个圆圈里面删除第m个数字,求出这个圆圈里最后剩下的数字。 +### 树的子结构 +考察二叉树的基本操作。输入2课二叉树A和B,判断B是不是A的子结构。 ``` -int last_remaining(unsigned int n,unsigned int m) +struct BinaryTreeNode{ + int m_value; + BinaryTreeNode *m_pleft; + BinaryTreeNode *m_pRight; +} ``` -### 求 1+2+...+n -要求不用乘除法,for/while/if/else/switch等关键字及条件判断语句 +``` + 8 + / \ 10 + / \ / \ + 6 10 子结构 11 9 + / \ / \ + 5 7 9 11 +``` + +``` +bool subTree(BinaryTreeNode *root1,BinaryTreeNode *root2); +``` + +### 二叉树翻转 + ``` -long long sum(unsigned int n); + 8 8 + / \ / \ + / \ / \ + 6 10 翻转后 10 6 + / \ / \ / \ / \ + 5 7 9 11 11 9 7 5 +``` + +`交换每个节点的左右子树` + +``` +void reverse(BinaryTreeNode *root); ``` -### 不用加减乘除做加法 -求2个整数之和 -`位运算` +### 从上往下打印二叉树 +`辅助队列` ``` -int sum(int,int) +void print_binary_level(BinaryTreeNode *root) ``` -### 不能被继承的类 +### 二叉搜索树的后续遍历序列 + +输入一个整数数组,判断该数组是不是某二叉查找树的后续遍历序列的结果。比如【 5,7,6,9,11,10,8】 是下面二叉查找树的后续遍历结果: ``` + 8 + / \ + / \ + 6 10 + / \ / \ + 5 7 9 11 +``` + +`寻找规律` ``` +bool is_post_order(BST *root,int *data, int length); +``` -### 把字符串转换成整数 +### 二叉树中和为某一值的路径 -比如 "12343567754" -> 12343567754 +``` + 10 + / \ + / \ + 5 12 + / \ + 5 7 +``` + +和为22的路径有2条:10--5--7, 10--12 + +`递归,栈` + +``` +void print_path(BinaryTree *root,int n) +``` -`NULL,空串,正负号,溢出` +### 二叉搜索树与双向链表 + +将二叉搜索树转换成一个排序的双向链表,只调整树中节点的指针指向 + +`递归` `分解问题` + ``` -int strToInt(char str); +BST *transform(BST *root); ``` + + +### 二叉树的深度 + +`递归` + + +``` +int tree_depth(BTree *root); +``` + + + ### 树中2个结点的最低公共祖先 如果这个树是二叉排序树 @@ -552,7 +606,19 @@ int strToInt(char str); +# 其他 + + +### 不能被继承的类 + +``` + +``` +### 实现Singleton模式 + + +### 赋值运算符函数 diff --git "a/Algorithms Job Interview/\347\274\226\347\250\213\344\271\213\347\276\216/README.md" "b/9 Algorithms Job Interview/\347\274\226\347\250\213\344\271\213\347\276\216/README.md" similarity index 93% rename from "Algorithms Job Interview/\347\274\226\347\250\213\344\271\213\347\276\216/README.md" rename to "9 Algorithms Job Interview/\347\274\226\347\250\213\344\271\213\347\276\216/README.md" index a9d3bd3..644f8a3 100644 --- "a/Algorithms Job Interview/\347\274\226\347\250\213\344\271\213\347\276\216/README.md" +++ "b/9 Algorithms Job Interview/\347\274\226\347\250\213\344\271\213\347\276\216/README.md" @@ -1,3 +1,4 @@ +#《编程之美》 书中的内容分为4个部分: @@ -7,17 +8,18 @@ 4. 数学之趣: 一些数学问题 《剑指offer》中已经出现的题目先不写了 + +# 游戏之乐 -### 游戏之乐 -#### -### 数字之魅 +# 数字之魅 #### 二进制数中1的个数 + #### 阶乘 @@ -73,7 +75,7 @@ -### 结构之法 +# 结构之法 #### 字符串移位包含的问题 @@ -109,8 +111,7 @@ - -### 数学之趣 +# 数学之趣 #### diff --git a/Big Data/Bitmap.md b/91 Algorithms In Big Data/Bitmap.md similarity index 78% rename from Big Data/Bitmap.md rename to 91 Algorithms In Big Data/Bitmap.md index d2ce2d3..94ac1f4 100644 --- a/Big Data/Bitmap.md +++ b/91 Algorithms In Big Data/Bitmap.md @@ -1,16 +1,16 @@ - - -## Bitmap +# Bitmap 也就是用1个(或几个)bit位来标记某个元素对应的value(如果是1bitmap,就只能是元素是否存在;如果是x-bitmap,还可以是元素出现的次数等信息)。使用bit位来存储信息,在需要的存储空间方面可以大大节省。应用场景有: -1. 排序(如果是1-bitmap,就只能对无重复的数排序) -2. 判断某个元素是否存在 +1. 判断某个元素是否存在 +2. 排序(如果是1-bitmap,就只能对无重复的数排序) + 比如,某文件中有若干8位数字的电话号码,要求统计一共有多少个不同的电话号码? -分析:8位最多99 999 999, 如果1Byte表示1个号码是否存在,需要95MB空间,但是如果1bit表示1个号码是否存在,则只需要 95/8=12MB 的空间。这时,数字k(0~99 999 999)与bit位的对应关系是: +分析:8位最多99 999 999, 如果1Byte表示1个号码是否存在,需要95MB空间,但是如果1bit表示1个号码是否存在,则只需要 95/8=12MB 的空间。这时,数字 `k(0~99 999 999)`与bit位的对应关系是: + ``` #define SIZE 15*1024*1024 diff --git a/91 Algorithms In Big Data/Bloomfilter.md b/91 Algorithms In Big Data/Bloomfilter.md new file mode 100644 index 0000000..c0075aa --- /dev/null +++ b/91 Algorithms In Big Data/Bloomfilter.md @@ -0,0 +1,231 @@ +# Bloom filter(布隆过滤器) + +Bloom Filter是由Bloom在1970年提出的一种多哈希函数映射的快速查找算法。通常应用在海量数据处理中,一些需要快速判断某个元素是否属于集合,但是并不严格要求100%正确的场合(容忍错误的场景)。 + + +## Bloom filter 特点 + +为了说明Bloom Filter存在的重要意义,举一个实例:假设要你写一个网络蜘蛛(web crawler)。由于网络间的链接错综复杂,蜘蛛在网络间爬行很可能会形成“环”。为了避免形成“环”,就需要知道蜘蛛已经访问过那些URL。给一个URL,怎样知道蜘蛛是否已经访问过呢?稍微想想,就会有如下几种方案: + +1. 将访问过的URL保存到数据库。 +2. 用HashSet将访问过的URL保存起来。那只需接近O(1)的代价就可以查到一个URL是否被访问过了。 +3. URL经过MD5或SHA-1等单向哈希后再保存到HashSet或数据库。 +4. BitMap方法。建立一个BitSet,将每个URL经过一个哈希函数映射到某一位。 + +方法1~3都是将访问过的URL完整保存,方法4则只标记URL的一个映射位。以上方法在数据量较小的情况下都能完美解决问题,但是当数据量变得非常庞大时问题就来了。 + +方法1的缺点:数据量变得非常庞大后关系型数据库查询的效率会变得很低。而且每来一个URL就启动一次数据库查询是不是太小题大做了? +方法2的缺点:太消耗内存。随着URL的增多,占用的内存会越来越多。就算只有1亿个URL,每个URL只算50个字符,就需要5GB内存。 +方法3:由于字符串经过MD5处理后的信息摘要长度只有128Bit,SHA-1处理后也只有160Bit,因此方法3比方法2节省了好几倍的内存。 +方法4消耗内存是相对较少的,但缺点是单一哈希函数发生冲突的概率太高。还记得数据结构课上学过的Hash表冲突的各种解决方法么?若要降低冲突发生的概率到1%,就要将BitSet的长度设置为URL个数的100倍。 + +实质上上面的算法都忽略了一个重要的隐含条件:允许小概率的出错,不一定要100%准确!也就是说少量url实际上没有没网络蜘蛛访问,而将它们错判为已访问的代价是很小的——大不了少抓几个网页呗。 + + +## 应用场景 + +* 爬虫对URL 去重,避免爬重复的URL; 爬虫Url重复(去重):如果几个亿几十亿的url装在一个集合上 ,比较浪费空间,把url映射到布隆过滤器,纯新的url一定会爬取, 少部分(比如0.01%)被判断为重复url可能也是新url ,会缺掉一些网页而已。 +* 垃圾邮箱问题,垃圾邮箱地址映射到bloomfilter,如果是垃圾邮箱,一定会被抓,这个能保证。无非是一些好人也被抓,这个可以通过给这些可伶的被误伤的设置个白名单就OK +* 避免缓存穿透,使用BloomFilter把所有数据放到bit数组中,当用户请求时存在的值肯定能放行,部分不存在的值也会被放行,绝大部分会被拦截。在DSP广告系统中,使用bloomfilter减少对Redis缓存读取,通过设备id读取用户信息( key为设备id,value hashmap 用户信息) ; 由于大量设备id都不是滴滴用户 (80%以上),redis中都没有用户信息,因此会产生大量无效redis读取。使用BF可以减轻Redis读取压力。 +* 减少磁盘IO: Google bigtable, Apache HBase 使用 BloomFilter 防止不必要的磁盘IO +* 减少网络请求:相同请求拦截,防止被攻击,也就是去重 + +Redis 4.0 中通过 布隆过滤器插件 来支持,redis 布隆过滤器主要就两个命令: + +* bf.add 添加元素到布隆过滤器中:bf.add urls https://jaychen.cc。 +* bf.exists 判断某个元素是否在过滤器中:bf.exists urls https://jaychen.cc。 + + +### 比特币 SPV钱包 + +比特币 SPV(simple payment verification)钱包应用中 使用 BloomFilter 加速钱包同步。 + +spv 主要用于移动支付的场景,不可能下载所有全节点数据,几百G + +在2012年 BIP37之前,SPV的做法是将所有的区块和交易都下载下来,然后本地将不相关的交易给删掉。当然带来的问题就是同步慢、浪费带宽、增加内存使用。在BIP-37中就提到了因为这一点,导致用户对手机APP“Bitcoin Wallet”有所抱怨。 + + +钱包余额多少? + +* 保护隐私,SPV节点不用告诉相邻全节点自己的所有钱包地址,而只是说一个 可能存在于bloomfilter里的钱包地址集合 +* 通过bloomfilter 过滤出 utxo,可能属于钱包地址,不在bloomfilter中地址对应的utxo一定会被过滤 + + + +## Bloom filter 算法 + + + Bloom filter bitmap 数组 和 k个hash函数。 + +使用 bitmap 位图数据结构,本质是一个bit位数组, 用 一个 bit 位标记对应Value的 取值(0 or 1); 判断一个值是否存在,就是看对应的bitmap位是否为 1 + +布隆过滤器还需要有 k 个哈希函数,进行如下操作: + +* 使用 K 个哈希函数对元素值进行 K 次计算,得到 K 个哈希值。 +* 根据得到的哈希值,在位数组中把对应下标的值置为 1。 + +比如有个url https://jaychen.cc , 有 3 个哈希函数:f1, f2, f3 和一个位数组 arr , 现在要把 url 插入布隆过滤器中: + +* 对值进行三次哈希计算,得到三个值 n1, n2, n3。 +* 把位数组中三个元素 arr[n1], arr[n2], arr[3] 置为 1。 + + + + + +现在需求是:要判断一个 url 是否在, 布隆过滤器中,对元素再次进行哈希计算,得到值之后判断位数组中的每个元素是否都为 1,如果值都为 1,那么说明这个值很大可能在布隆过滤器中;如果存在一个值不为 1,说明该元素肯定不在布隆过滤器中。 + +Scrapy-Redis的去重机制中,一个url指纹存储未 40长度的16 进制数,如27adcc2e8979cdee0c9cecbbe8bf8ff51edefb61 +占用 20Byte内存空间,1亿个指纹占用2 GB + + + +Bloom filter可以看做是对bitmap的扩展。只是使用多个hash映射函数,从而减低hash发生冲突的概率。算法如下: + +1. 创建 m 位的bitset,初始化为0, 选中k个不同的哈希函数 +2. 第 i 个hash 函数对字符串str 哈希的结果记为 h(i,str) ,范围是(0,m-1) +3. 将字符串记录到bitset的过程:对于一个字符串str,分别记录h(1,str),h(2,str)...,h(k,str)。 然后将bitset的h(1,str),h(2,str)...,h(k,str)位置1。也就是将一个str映射到bitset的 k 个二进制位。 + +4. 检查字符串是否存在:对于字符串str,分别计算h(1,str)、h(2,str),...,h(k,str)。然后检查BitSet的第h(1,str)、h(2,str),...,h(k,str) 位是否为1,若其中任何一位不为1则可以判定str一定没有被记录过。若全部位都是1,则“认为”字符串str存在。但是若一个字符串对应的Bit全为1,实际上是不能100%的肯定该字符串被Bloom Filter记录过的。(因为有可能该字符串的所有位都刚好是被其他字符串所对应)这种将该字符串划分错的情况,称为false positive 。 + +5. 删除字符串:字符串加入了就被不能删除了,因为删除会影响到其他字符串。实在需要删除字符串的可以使用Counting bloomfilter(CBF)。 + + +`Bloom Filter 使用了k个哈希函数,每个字符串跟k个bit对应。从而降低了冲突的概率。` + + + +### 最优的哈希函数个数,位数组m大小 + +哈希函数的选择对性能的影响应该是很大的,一个好的哈希函数要能近似等概率的将字符串映射到各个Bit。选择k个不同的哈希函数比较麻烦,一种简单的方法是选择一个哈希函数,然后送入k个不同的参数。 + +在原始个数位n时,那这里的k应该取多少呢?位数组m大小应该取多少呢?这里有个计算公式:`k=(ln2)*(m/n)`, 当满足这个条件时,错误率最小。 + + +假设错误率为0.01, 此时m 大概是 n 的13倍,k大概是8个。 这里的n是元素记录的个数,m是bit位个数。如果每个元素的长度原大于13,使用Bloom Filter就可以节省内存。 + + +### 错误率估计 + + + +## 实现示例 + +``` +#define SIZE 15*1024*1024 +char a[SIZE]; /* 15MB*8 = 120M bit空间 */ +memset(a,0,SIZE); + +int seeds[] = { 5, 7, 11, 13, 31, 37, 61}; + +int hashcode(int cap,int seed, string key){ + int hash = 0; + for (int i=0;i>> 16)) & Integer.MAX_VALUE; + } + +} +``` + + +``` +public class ByteBufferBloomFilter { + + /** + * 存储BloomFilter数据 + */ + private final ByteBuffer data; + + private final int size;//占用空间 + + + /** + * 构造BloomFilter + * @param size 占用空间(字节数),应设为key总数的1.5倍以上,最大不超过2G + */ + public ByteBufferBloomFilter(int size) { + if (size <= 0) { + throw new IllegalArgumentException("size must > 0"); + } + this.size = size; + this.data = ByteBuffer.allocateDirect(size); + } + + + @Override + public void put(String key) { + int[] hs = Hash.hashes(key, size); + for (int i = 0; i < hs.length; i++) { + int idx = hs[i]; + int b = data.get(idx); + data.put(idx, (byte) (b | (1 << i))); + } + } + + @Override + public boolean contains(String key) { + int[] hs = Hash.hashes(key, size); + for (int i = 0; i < hs.length; i++) { + int b = data.get(hs[i]); + if ((b & (1 << i)) == 0) { + return false; + } + } + return true; + } + + + @Override + public int size() { + return size; + } +} +``` + +对每个字符串str求哈希就可以使用 `hashcode(SIZE*8,seeds[i],str)` ,i 的取值范围就是 (0,k)。 + + + + + +## 参考 + +http://www.cnblogs.com/heaad/archive/2011/01/02/1924195.html +http://blog.csdn.net/jiaomeng/article/details/1495500 +http://pages.cs.wisc.edu/~cao/papers/summary-cache/node8.html `哈希函数个数k、位数组大小m` 测试论证 +https://blog.csdn.net/tianyaleixiaowu/article/details/74721877 +https://juejin.im/post/5bc7446e5188255c791b3360 + + + diff --git "a/Big Data/Hash\346\230\240\345\260\204,\345\210\206\350\200\214\346\262\273\344\271\213.md" "b/91 Algorithms In Big Data/Hash\346\230\240\345\260\204,\345\210\206\350\200\214\346\262\273\344\271\213.md" similarity index 96% rename from "Big Data/Hash\346\230\240\345\260\204,\345\210\206\350\200\214\346\262\273\344\271\213.md" rename to "91 Algorithms In Big Data/Hash\346\230\240\345\260\204,\345\210\206\350\200\214\346\262\273\344\271\213.md" index afbdcfb..a964710 100644 --- "a/Big Data/Hash\346\230\240\345\260\204,\345\210\206\350\200\214\346\262\273\344\271\213.md" +++ "b/91 Algorithms In Big Data/Hash\346\230\240\345\260\204,\345\210\206\350\200\214\346\262\273\344\271\213.md" @@ -1,5 +1,4 @@ - -## Hash映射,分而治之 +# Hash映射,分而治之 这里的`Hash映射`是指通过一种映射散列的方式,将海量数据均匀分布在对应的内存或更小的文件中 diff --git a/Big Data/btree_insert.gif b/91 Algorithms In Big Data/Inverted Index/btree_insert.gif similarity index 100% rename from Big Data/btree_insert.gif rename to 91 Algorithms In Big Data/Inverted Index/btree_insert.gif diff --git a/Big Data/disk_search.png b/91 Algorithms In Big Data/Inverted Index/disk_search.png similarity index 100% rename from Big Data/disk_search.png rename to 91 Algorithms In Big Data/Inverted Index/disk_search.png diff --git "a/91 Algorithms In Big Data/Inverted Index/\345\200\222\346\216\222\347\264\242\345\274\225(Inverted Index).md" "b/91 Algorithms In Big Data/Inverted Index/\345\200\222\346\216\222\347\264\242\345\274\225(Inverted Index).md" new file mode 100644 index 0000000..3c83445 --- /dev/null +++ "b/91 Algorithms In Big Data/Inverted Index/\345\200\222\346\216\222\347\264\242\345\274\225(Inverted Index).md" @@ -0,0 +1,54 @@ +# 倒排索引(Inverted Index) + + +常规的索引是文档到关键词的映射,就是每个文档指向一个它所包含的索引项的序列,也就是文档文档指向了它包含的索引项序列,也就是文档指向它包含的哪些单词。 + +倒排索引也叫反向索引。是文档检索系统中最常用的数据结构。被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射。 +常被应用于搜索引擎和关键字查询的问题中。比如文档检索系统中,查询哪些文件包含了某个单词。 + +而反向索引则是单词(关键词)指向包含了它的文档们。比如: + +``` +T0 = "it is what it is" +T1 = "what is it" +T2 = "it is a banana" +``` + +得到的反向文件索引是: + +``` +"a": {2} +"banana": {2} +"is": {0, 1, 2} +"it": {0, 1, 2} +"what": {0, 1} +``` + +当我们以”what” “is” “it”检索条件去查询时,结果集是:{0,1}∩{0,1,2}∩{0,1,2} = {0,1} 。 +也就是结果集为文档0和文档1,但其实精确匹配的是文档1。 + + +### 数据结构 + +- 将文章提炼关键词,且每个关键词都对应这篇文章 +- 在加入新文章时,我们需要为新文章中的关键词索引和之前的合并 + + +### 应用场景 + +* mysql中的索引实现,使用 B+ 树实现 +* ES +* 推荐内容召回 + + + + + +### 参考 + +[Elasticsearch](https://github.com/elastic/elasticsearch) 就是使用倒排索引(inverted index)的结构来做快速的全文搜索; ElasticSearch 不仅用于全文搜索, 还有非常强大的统计功能 (facets),携程,58,美团的分享中都提到ES构建实时日志系统,帮助定位系统问题。 + + +[Elasticsearch权威指南](http://es.xiaoleilu.com/index.html) + + diff --git "a/Big Data/\346\225\260\346\215\256\345\272\223\347\264\242\345\274\225.md" "b/91 Algorithms In Big Data/Inverted Index/\346\225\260\346\215\256\345\272\223\347\264\242\345\274\225.md" similarity index 96% rename from "Big Data/\346\225\260\346\215\256\345\272\223\347\264\242\345\274\225.md" rename to "91 Algorithms In Big Data/Inverted Index/\346\225\260\346\215\256\345\272\223\347\264\242\345\274\225.md" index 3280fea..96b8cf0 100644 --- "a/Big Data/\346\225\260\346\215\256\345\272\223\347\264\242\345\274\225.md" +++ "b/91 Algorithms In Big Data/Inverted Index/\346\225\260\346\215\256\345\272\223\347\264\242\345\274\225.md" @@ -1,5 +1,4 @@ - -## 数据库索引 +# 数据库索引 索引使用的数据结构多是B树或B+树。B树和B+树广泛应用于文件存储系统和数据库系统中,mysql使用的是B+树,oracle使用的是B树,Mysql也支持多种索引类型,如b-tree 索引,哈希索引,全文索引等。 @@ -118,7 +117,7 @@ d=100时,h差不多3个 沿着搜索的路径从root一直到叶节点 -每个节点的关键字个数在【d-1,2d-1】之间,当节点的关键字个数是2t-1时,再加入target就违反了B树定义,需要对该节点进行分裂:已中间节点为界,分成2个包含d-1个关键字的子节点(另外还有一个分界关键字,2*(d-1)+1=2d-1),同时把该分界关键字提升到该叶子的父节点中,如果这导致父节点关键字个数超过2d-1,就继续向上分裂,直到根节点。 +每个节点的关键字个数在【d-1,2d-1】之间,当节点的关键字个数是2t-1时,再加入target就违反了B树定义,需要对该节点进行分裂:已中间节点为界,分成2个包含 `d-1` 个关键字的子节点(另外还有一个分界关键字,`2*(d-1)+1=2d-1)`,同时把该分界关键字提升到该叶子的父节点中,如果这导致父节点关键字个数超过 `2d-1`, 就继续向上分裂,直到根节点。 如下演示动画,往度d=2的B树中插入:` 6 10 4 14 5 11 15 3 2 12 1 7 8 8 6 3 6 21 5 15 15 6 32 23 45 65 7 8 6 5 4` diff --git a/Big Data/README.md b/91 Algorithms In Big Data/README.md similarity index 76% rename from Big Data/README.md rename to 91 Algorithms In Big Data/README.md index b7731ea..b45e6b5 100644 --- a/Big Data/README.md +++ b/91 Algorithms In Big Data/README.md @@ -1,5 +1,4 @@ - -## 海量数据处理 +# 海量数据处理 所谓海量数据,就是数据量太大,要么在短时间内无法计算出结果,要么数据太大,无法一次性装入内存。 @@ -7,15 +6,15 @@ 针对空间,就一个办法,大而化小,分而治之。常采用hash映射 -* Hash映射/分而治之 -* Bitmap -* Bloom filter(布隆过滤器) -* 双层桶划分 -* Trie树 -* 数据库索引 +* [Hash映射,分而治之](Hash映射,分而治之.md) +* [Bitmap](Bitmap.md) +* [Bloom filter(布隆过滤器)](Bloomfilter.md) +* [双层桶划分](双层桶划分.md) +* [Trie树](../4%20Tree/4-字典树Trie/README.md) +* [数据库索引](Inverted%20Index/数据库索引.md) * 倒排索引(Inverted Index) -* 外排序 -* simhash算法 +* [外排序](../6%20Sort/外排序.md) +* [simhash算法](simhash算法.md) * 分布处理之Mapreduce diff --git "a/91 Algorithms In Big Data/mapreduce/Hash\346\230\240\345\260\204,\345\210\206\350\200\214\346\262\273\344\271\213.md" "b/91 Algorithms In Big Data/mapreduce/Hash\346\230\240\345\260\204,\345\210\206\350\200\214\346\262\273\344\271\213.md" new file mode 100644 index 0000000..a964710 --- /dev/null +++ "b/91 Algorithms In Big Data/mapreduce/Hash\346\230\240\345\260\204,\345\210\206\350\200\214\346\262\273\344\271\213.md" @@ -0,0 +1,20 @@ +# Hash映射,分而治之 + +这里的`Hash映射`是指通过一种映射散列的方式,将海量数据均匀分布在对应的内存或更小的文件中 + +使用hash映射有个最重要的特点是: `hash值相同的两个串不一定一样,但是两个一样的字符串hash值一定相等`。哈希函数如下: + +``` +int hash = 0; +for (int i=0;iDijkstra是荷兰的计算机科学家,提出”信号量和PV原语“,"解决哲学家就餐问题",”死锁“也是它提出来的 - * 动态规划 (Dynamic Programming) * BFS/DFS (广度/深度优先遍历) * 红黑树 一种自平衡的`二叉查找树` @@ -17,5 +14,9 @@ * Hash * 快速排序 * SPFA(shortest path faster algorithm) 单元最短路径算法 -* 快递选择SELECT +* 快递选择SELECT + + + + diff --git a/Algorithms Analysis/README.md b/Algorithms Analysis/README.md deleted file mode 100644 index 3aaddf0..0000000 --- a/Algorithms Analysis/README.md +++ /dev/null @@ -1,35 +0,0 @@ - -## 算法分析思路 - -详细介绍每一种算法设计的思路,并为每种方法给出一个经典案例的详细解读,总结对应设计思路,最后给出其它案例,以供参考。 - - -* 迭代法 -* 穷举搜索法 -* 动态规划 -* 贪心算法 -* 回溯法 -* 分治算法 -* 递归 - - - -### 总结 - -贪心法、分治法、动态规划都是将问题归纳为根小的、相似的子问题,通过求解子问题产生全局最优解。 - -`贪心法` - -`分治法` - -`动态规划` - - - -## 参考 - -《算法设计与分析基础》 Anany Levitin -http://www.chinaunix.net/old_jh/23/437639.html - - - diff --git "a/Algorithms Analysis/\345\210\206\346\262\273\347\256\227\346\263\225.md" "b/Algorithms Analysis/\345\210\206\346\262\273\347\256\227\346\263\225.md" deleted file mode 100644 index 87aa1a2..0000000 --- "a/Algorithms Analysis/\345\210\206\346\262\273\347\256\227\346\263\225.md" +++ /dev/null @@ -1,30 +0,0 @@ - -### 分治算法 - - -将一个难以直接解决的大问题,分割成一些规模较小的相同问题,各个击破,分而治之。 - -分治算法常用`递归`实现 - -1) 问题缩小的小规模可以很容易解决 -2) 问题可以分解为规模较小相同问题 -3) 子问题的解可以合并为该问题的解 -4) 各个子问题相互独立,(如果这条不满足,转为`动态规划`求解) - -分治法的步骤: -1. 分解 -2. 解决 -3. 合并 - - -#### 大整数乘法 - -如 26542123532213598*345987342245553677884 - - - -#### 其它案例 - -快速排序 -归并排序 -最大子数组和 diff --git "a/Algorithms Analysis/\345\212\250\346\200\201\350\247\204\345\210\222.md" "b/Algorithms Analysis/\345\212\250\346\200\201\350\247\204\345\210\222.md" deleted file mode 100644 index cd14e8d..0000000 --- "a/Algorithms Analysis/\345\212\250\346\200\201\350\247\204\345\210\222.md" +++ /dev/null @@ -1,34 +0,0 @@ - - -## 动态规划DP - -复杂问题不能分解成几个子问题,而分解成一系列子问题; - -DP通常基于一个递推公式及一个(或多个)初始状态,当前子问题解由上一次子问题解推出。 - -状态 -状态转移方程 -递推关系 - -动态规划算法的关键在于解决冗余,以空间换时间的技术,需要存储过程中的各种状态。可以看着是`分治算法`+`解决冗余` - -使用动态规划算法的问题的特征是`子问题的重叠性`,否则动态规划算法不具备优势 - - -####基本步骤 - -1. 划分问题 -2. 选择状态 -3. 确定决策并写出状态转移方程 -4. 写出规划方程 - - -#### 最长递增子序列 - -最长递增子序列(LIS Longest Increasing Subsequence) - - -#### 其它案例 - -最短路径 - diff --git "a/Algorithms Analysis/\345\233\236\346\272\257\346\263\225.md" "b/Algorithms Analysis/\345\233\236\346\272\257\346\263\225.md" deleted file mode 100644 index e0836c1..0000000 --- "a/Algorithms Analysis/\345\233\236\346\272\257\346\263\225.md" +++ /dev/null @@ -1,31 +0,0 @@ - - -## 回溯法 - -也叫 `试探法`。 是一种选优搜索法,按照选优条件搜索,当搜索到某一步,发现原先选择并不优或达不到目标,就退回重新选择。 - - -一般步骤 - -1. 针对问题,定义解空间( 这时候解空间是一个集合,且包含我们要找的最优解) -2. 组织解空间,确定易于搜索的解空间结构,通常组织成`树结构` 或 `图结构` -3. 深度优先搜索解空间,搜索过程中用剪枝函数避免无效搜索 - -回溯法求解问题时,一般是一边建树,一边遍历该树;且采用非递归方法。 - - -#### 八皇后问题 - -8x8的国际象棋棋盘上放置8个皇后,使得任何一个皇后都无法直接吃掉其他的皇后。任意2个皇后都不能处于同一个 横线,纵线,斜线上。 - -分析 -1. 任意2个皇后不能同一行,也就是每个皇后占据一行,通用的,每个皇后也要占据一列 -2. 一个斜线上也只有一个皇后 - - - -#### 其它案例 - -迷宫问题 - - diff --git "a/Algorithms Analysis/\350\264\252\345\277\203\347\256\227\346\263\225.md" "b/Algorithms Analysis/\350\264\252\345\277\203\347\256\227\346\263\225.md" deleted file mode 100644 index da6d18a..0000000 --- "a/Algorithms Analysis/\350\264\252\345\277\203\347\256\227\346\263\225.md" +++ /dev/null @@ -1,17 +0,0 @@ - - -## 贪心算法 - -不追求最优解,只找到满意解。 - - -#### 赫夫曼编码 - - - -#### 其它案例 - -找回零钱问题 -装箱问题 - - diff --git "a/Algorithms Analysis/\351\200\222\345\275\222.md" "b/Algorithms Analysis/\351\200\222\345\275\222.md" deleted file mode 100644 index 3a11497..0000000 --- "a/Algorithms Analysis/\351\200\222\345\275\222.md" +++ /dev/null @@ -1,37 +0,0 @@ - - -## 递归 - -递归是一种设计和描述算法的有力工具。 递归算法执行过程分 `递推` 和 `回归` 两个阶段 - -在 `递推` 阶段,将大的问题分解成小的问题 -在 `回归` 阶段,获得最简单问题的解后,逐级返回,依次得到稍微复杂情况的解,知道获得最终的结果 - -1) 确定递归公式 -2) 确定边界条件 - - -#### 斐波那契数列 - -fib(n)=fib(n-1)+fib(n-2) - - -递归实现 -``` - -``` - -非递归实现 -``` - -``` - -#### 其它案例 - -阶乘计算 -梵塔问题 (三根针1,2,3表示,1号从小到大n个盘子,先要都移到3号上,不能出现大盘压小盘,找出移动次数最少的方案) -快速排序 - - -递归运行效率较低,因为有函数调用的开销,递归多次也可能造成栈溢出。 - diff --git a/Algorithms In Open Source/YYCache.md b/Algorithms In Open Source/YYCache.md deleted file mode 100644 index 9e89a78..0000000 --- a/Algorithms In Open Source/YYCache.md +++ /dev/null @@ -1,40 +0,0 @@ - -## YYCache - -[YYCache](https://github.com/ibireme/YYCache.git) 是 iOS 系统上一套线程安全的 `Key-Value` 缓存实现,使用 `Objective-C` 语言实现。`YYCache` 使用 `双向链表队列+hash表结构` 实现。 - -### 用到的算法介绍 - -先来看一下它的数据结构: - -``` -// 这是一个节点的结构 -@interface _YYLinkedMapNode : NSObject { - @package - __unsafe_unretained _YYLinkedMapNode *_prev; // retained by dic - __unsafe_unretained _YYLinkedMapNode *_next; // retained by dic - id _key; - id _value; - NSUInteger _cost; - NSTimeInterval _time; -} -``` - -这里定义了一个双向链表结构,`_prev`,`_next`分别指向前缀节点和后缀节点。 - -``` -@interface _YYLinkedMap : NSObject { - @package - CFMutableDictionaryRef _dic; // do not set object directly - NSUInteger _totalCost; - NSUInteger _totalCount; - _YYLinkedMapNode *_head; // MRU, do not change it directly - _YYLinkedMapNode *_tail; // LRU, do not change it directly - BOOL _releaseOnMainThread; - BOOL _releaseAsynchronously; -} -``` -_dic 就是存储缓存节点的hash结构 -_head 指向链表的头部,_tail指向链表的尾部,组成一个队列结构 - - diff --git a/Algorithms Job Interview/README.md b/Algorithms Job Interview/README.md deleted file mode 100644 index 59c02b8..0000000 --- a/Algorithms Job Interview/README.md +++ /dev/null @@ -1,106 +0,0 @@ - -这部分内容是算法问题合集,题目大多来自网络和书籍。我做了下简单的整理,很多题做了一些思路标记。 - - -### 字符串 - -计算机处理的数据除了数值,就是字符。字符处理的常见问题包括: - -* 单词反转 -* 回文判断 -* 字符串的压缩 -* 字符串的排列和组合 -* 字符串比较 -* 子串 - - - -### 链表 - -链表处理的常见问题包括: - -* 链表反转 -* 链表中是否有环 -* 删除链表中的p节点,在p节点前面插入节点q, 要求O(1)复杂度 -* 2个链表相交 -* 2个链表合并 - - -### 栈和队列 - -这是最基本数据结构,相对容易理解。 - - - -### 数值问题 - -这部分都是一些数学几何计算方面的问题。 主要有: - -* 位运算 -* 随机数 -* 大数问题 - - -### 数组数列问题 - - -这部分的问题都集中在数据集合上。主要有: - -* 数组排序 -* top-k -* 子数组 -* 多个数组合并,交集 - - -### 矩阵 - -这部分都是矩阵和二维数组相关的问题。 - - -### 二叉树 - - -* 遍历 -* 翻转 -* 子树 - - - -### 图 - -图相关的问题 - - -### 海量数据处理 - - -海量处理问题常用的分析解决问题的思路是: - -* Hash映射/分而治之 -* Bitmap -* Bloom filter(布隆过滤器) -* Trie树 -* 数据库索引 -* 倒排索引(Inverted Index) -* 双层桶划分 -* 外排序 -* simhash算法 -* 分布处理之Mapreduce - - -### 智力思维训练 - - - -###【剑指offer】 - - -《剑指offer》里面给出了50到高质量的算法问题,很有学习的必要。 - - -###【编程之美】 - - - - - diff --git a/Algorithms Job Interview/leetcode.md b/Algorithms Job Interview/leetcode.md deleted file mode 100644 index 634b1fb..0000000 --- a/Algorithms Job Interview/leetcode.md +++ /dev/null @@ -1,13 +0,0 @@ - -题目来自[leetcode](https://leetcode.com/) - - -[LeetCode题解C++版](https://github.com/soulmachine/leetcode), 151道题完整版 - -#### Minimum Height Trees - - - -#### Additive Number - - diff --git "a/Algorithms Job Interview/\344\271\235\345\272\246OJ.md" "b/Algorithms Job Interview/\344\271\235\345\272\246OJ.md" deleted file mode 100644 index b8f5dca..0000000 --- "a/Algorithms Job Interview/\344\271\235\345\272\246OJ.md" +++ /dev/null @@ -1,5 +0,0 @@ - -题目来自 [九度OJ](http://ac.jobdu.com/index.php) - - - diff --git "a/Algorithms Job Interview/\344\272\214\345\217\211\346\240\221.md" "b/Algorithms Job Interview/\344\272\214\345\217\211\346\240\221.md" deleted file mode 100644 index f6ddadc..0000000 --- "a/Algorithms Job Interview/\344\272\214\345\217\211\346\240\221.md" +++ /dev/null @@ -1,216 +0,0 @@ - -问题涉及有: - -* 遍历 -* 翻转 -* 子树 - - -#### 把二元查找树转变成排序的双向链表 - -输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表。要求`不能创建任何新的结点`,只调整指针的指向。 - -``` - 10 - / \ - 6 14 -/\ / \ -4 8 12 16 -``` - -转换成双向链表 `4=6=8=10=12=14=16` - -首先我们定义的二元查找树 节点的数据结构如下: -``` - struct BSTreeNode -{ - int m_nValue; // value of node - BSTreeNode *m_pLeft; // left child of node - BSTreeNode *m_pRight; // right child of node -}; -``` - -分析:题目要求不能创建任何节点,也就是只能调整各个节点的指向 -思路一: - - - -#### 在二元树中找出和为某一值的所有路径 - -题目:输入一个整数和一棵二元树。从树的根结点开始往下访问一直到叶结点所经过的所有结点形成一条路径。打印出和与输入整数相等的所有路径。 - -例如 输入整数22和如下二元树 -``` - 10 - / \ - 5 12 - /\ - 4 7 -``` -则打印出两条路径:`10, 12` 和 `10, 5, 7` 。 - -二元树节点的数据结构定义为: - -``` -struct BinaryTreeNode // a node in the binary tree -{ - int m_nValue; // value of node - BinaryTreeNode *m_pLeft; // left child of node - BinaryTreeNode *m_pRight; // right child of node -}; -``` - -1. 累加当前节点,累加和大于给定值 -2. 不为叶节点,左子树入栈 - - - -#### 判断整数序列是不是二元查找树的后序遍历结果 - -题目:输入一个整数数组,判断该数组是不是某二元查找树的后序遍历的结果。 -如果是返回true,否则返回false。 -例如输入5、7、6、9、11、10、8,由于这一整数序列是如下树的后序遍历结果: -``` - 8 - / \ - 6 10 - /\ / \ - 5 7 9 11 -``` -因此返回true。如果输入7、4、6、5,没有哪棵树的后序遍历的结果是这个序列,因此返回false。 - - - - -#### 一棵排序二叉树,令 f=(最大值+最小值)/2,设计一个算法,找出距离f值最近、大于f值的结点。 -复杂度如果是O(n2)则不得分。 - - - - -#### 二叉树翻转 - -输入一颗二元查找树,将该树转换为它的镜像,即在转换后的二元查找树中,左子树的结点都大于右子树的结点。用递归和循环两种方法完成树的镜像转换。例 - -如输入: -``` - 8 - / \ - 6 10 - /\ / \ -5 7 9 11 -``` -输出: - -``` - 8 - / \ - 10 6 - /\ /\ -11 9 7 5 -``` -定义二元查找树的结点为: -``` -struct BSTreeNode // a node in the binary search tree (BST) -{ - int m_nValue; // value of node - BSTreeNode *m_pLeft; // left child of node - BSTreeNode *m_pRight; // right child of node -}; -``` - - -#### 按层打印二叉树 - -输入一颗二元树,从上往下按层打印树的每个结点,同一层中按照从左往右的顺序打印。 -例如输入 - -``` - 8 - /\ - 6 10 -/ \ /\ -5 7 9 11 -``` - -输出`8 6 10 5 7 9 11` - - - - -#### 二元树的深度 - -题目:输入一棵二元树的根结点,求该树的深度.从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。 -例如:输入二元树: -``` - - 10 - / \ - 6 14 - / / \ - 4 12 16 -``` - -输出该树的深度3。 - -二元树的结点定义如下: -``` -struct SBinaryTreeNode // a node of the binary tree -{ - int m_nValue; // value of node - SBinaryTreeNode *m_pLeft; // left child of node - SBinaryTreeNode *m_pRight; // right child of node -}; -``` - -分析:这道题本质上还是考查二元树的遍历。对于一颗完全二叉树,要求给所有节点加上一个pNext指针,指向同一层的相邻节点;如果当前节点已经是该层的最后一个节点,则将pNext指针指向NULL;给出程序实现,并分析时间复杂度和空间复杂度。 - - - -#### 把一个有序整数数组放到二叉树中? - -分析:本题考察二叉搜索树的建树方法,简单的递归结构。 - -关于树的算法设计一定要联想到递归,因为树本身就是递归的定义。 -而,学会把递归改称非递归也是一种必要的技术。 -毕竟,递归会造成栈溢出,关于系统底层的程序中不到非不得以最好不要用。 -但是对某些数学问题,就一定要学会用递归去解决。 - - - -#### 二叉树两个结点的最低共同父结点 - -设计一个算法,找出二叉树上任意两个结点的最近共同父结点。复杂度如果是O(n2)则不得分 - -二叉树的结点定义如下: - -``` -struct TreeNode -{ - int m_nvalue; - TreeNode* m_pLeft; - TreeNode* m_pRight; -}; -``` - -输入二叉树中的两个结点,输出这两个结点在数中最低的共同父结点。 - -分析:求数中两个结点的最低共同结点是面试中经常出现的一个问题。这个问题至少有两个变种。 - - - - -#### 俩种方法实现二叉树的前序遍历。 - -递归和非递归 - - -#### 恢复树结构 - -有一棵树(树上结点为字符串或者整数),请写代码将树的结构和数据写到一个文件中,并能通过读取该文件恢复树结构。 - - - - - - diff --git "a/Algorithms Job Interview/\345\233\276.md" "b/Algorithms Job Interview/\345\233\276.md" deleted file mode 100644 index f8b18cf..0000000 --- "a/Algorithms Job Interview/\345\233\276.md" +++ /dev/null @@ -1,28 +0,0 @@ - - -31.华为面试题: -一类似于蜂窝的结构的图,进行搜索最短路径(要求5分钟) - - -求一个有向连通图的割点,割点的定义是,如果除去此节点和与其相关的边, -有向图不再连通,描述算法。 - - - - -11、平面上N个点,每两个点都确定一条直线, -求出斜率最大的那条直线所通过的两个点(斜率不存在的情况不考虑)。时间效率越高越好。 - - -2.求一个有向连通图的割点,割点的定义是, - -如果除去此节点和与其相关的边,有向图不再连通,描述算法。 - - - - - 求一个二叉树中任意两个节点间的最大距离, -两个节点的距离的定义是 这两个节点间边的个数, -比如某个孩子节点和父节点间的距离是1,和相邻兄弟节点间的距离是2,优化时间空间复杂度。 - - diff --git "a/Algorithms Job Interview/\345\255\227\347\254\246\344\270\262.md" "b/Algorithms Job Interview/\345\255\227\347\254\246\344\270\262.md" deleted file mode 100644 index fedd6ba..0000000 --- "a/Algorithms Job Interview/\345\255\227\347\254\246\344\270\262.md" +++ /dev/null @@ -1,599 +0,0 @@ - - -字符串常见的问题: - -* 单词反转/移动/回文判断 -* 字符(串)统计 -* 字符串的压缩 -* 字符串的排列和组合 -* 字符串比较 -* 子串 - - -#### 翻转句子中单词的顺序 - -题目:输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。句子中单词以空格符隔开。为简单起见,标点符号和普通字母一样处理。 -例如输入“I am a student.”,则输出“student. a am I”。 - -``` -char *revert_by_word(char *source); -``` - -原地逆序,字符串2边的字符逐个交换 , 再按单词逆序; 也可以先按单词逆序,再对整个句子逆序; - -针对不允许临时空间的情况,也就是字符交换不用临时空间,可以使用的方法有: - -1. 异或操作 -2. 也就是2个整数相互交换一个道理 - -``` -char a = 'a', b = 'b'; -a = a + b; -b = a - b; -a = a - b; -``` - -最终示例代码: - -``` -void _reverse(char *start,char *end){ - if ((start == NULL) || (end == NULL)) return; - while(start < end){ - char tmp = *start; - *start = *end; - *end = tmp; - - start++, - end--; - } -} - -char *revert_by_word(char *source){ - char *end = source; - char *start = source; - if (source == NULL) return NULL; - while (*end != '\0') end++; - end--; - - _reverse(start,end); - - start=end=source; - while(*start != '\0'){ - if (*start == ' '){ - start++; - end++; - }else if(*end == ' ' || *end == '\0'){ - _reverse(start,end-1); - start = end; - }else{ - end++; - } - } - return source; -} -``` - -类似的题目还有: - -不开辟用于交换数据的临时空间,如何完成字符串的逆序 -用C语言实现一个revert函数,它的功能是将输入的字符串在原串上倒序后返回。 - - -#### 左旋转字符串 - ->字符串的左旋转操作:把字符串前面的若干个字符移动到字符串的尾部。 - -如把字符串abcdef左旋转2位得到字符串cdefab。请实现字符串左旋转的函数。要求时间对长度为n的字符串操作的复杂度为O(n),辅助内存为O(1)。 - -``` -char *left_rotate(char *str,int offset){ - -} -``` -我们可以abcdef分成两部分,ab和cdef,内部逆序以后,整体再次逆序,就可以得到想要的结果了,也就是跟上面的问题是同样的问题。 - - -#### 判断字符串是否是回文 - -分析: 2个指针,一头一尾,逐个比较,都相同就是回文 - -``` -/* -* eg acdeedca -* @ret 0 success , -1 fail -*/ -int is_huiwen(const char *source){ - if (source == NULL || source == '\0') return -1; - char *head = source; - char *tail = source; - while(*tail != '\0'){ - tail++; - } - tail--; - - while(head < tail){ - if (*head != *tail){ - return -1; - } - head++; - tail--; - } - - return 0; -} -``` - - -#### 只出现一次的字符 - -在一个字符串中找到第一个只出现一次的字符。如输入ahbaccdeff,则输出h。 - -``` -char char_first_appear_once(const char *source) -``` - -思路一: 蛮力统计, O(n^2)复杂度 -思路二: 使用hash表,2次扫描,第一次建立hash表 key为字符,value为出现次数;第二次扫描找到第一个value为1的key,时间复杂度O(n) - -hash表长度 256,字符直接作为key值。需要注意的是 char 的范围是 -128~127,unsigned char 才是0~255 - - -示例代码: - -``` -char char_first_appear_once(const unsigned char *source){ - int hash[256]={0}; - char *tmp = source; - if (tmp == NULL) return '\0'; - while(*tmp != '\0'){ - hash[*tmp]++; - tmp++; - } - - tmp = source; - while(*tmp != '\0'){ - if (hash[*tmp] == 1) return *tmp; - tmp++; - } - return '\0'; -} -``` - - -题目扩展:这里的字符换成整数,整数数量几十TB,海量数据处理,显然hash方法不可能,没有那么大得内容 - - -#### 统计文章里单词出现的次数 - -设计相应的数据结构和算法,尽量高效的统计一片英文文章(总单词数目)里出现的所有英文单词,按照在文章中首次出现的顺序打印输出该单词和它的出现次数。 - -``` -void statistics(char *string) -void statistics(FILE *fd) -``` - - -延伸: - -如果是海量数据里面统计top-k次数的单词呢? - - -#### 替换空格 - -实现一个函数,把每个空格替换成 "%20",如输入“we are happy”,则输出“we%20are%20happy” - -``` -char *replce_blank(char *source) -``` - -主要问题是一个字符替换成3个字符,替换后的字符串比原串长。 -如果想要在原串上直接修改,就不能顺序替换。且原串的空间应该足够大,能容纳替换变长以后的字符串。如果空间不够,就要新建一块空间来保存替换的结果了。这里假设空间足够 - -1. 第一遍扫描,统计空格个数 n, 替换后的字符串长度 = 原长度+2*n -2. 从后向前扫描字符串,挪动每个字符的位置。注意碰到空格的地方 - - -``` -char *replace_blank(char *source){ - int count = 0; - char *tail = source; - if (source == NULL) return NULL; - while(*tail != '\0'){ - if (*tail == ' ') count++; - tail++; - } - - while(count){ - if(*tail != ' '){ - *(tail+2*count) = *tail; - }else{ - *(tail+2*count) = '0'; - *(tail+2*count-1) = '2'; - *(tail+2*count-2) = '%'; - count--; - } - tail--; - } - - return source; -} -``` - - -#### 小写字母排在大写字母的前面 - -有一个由大小写组成的字符串,现在需要对他进行修改,将其中的所有小写字母排在大写字母的前面(大写或小写字母之间不要求保持原来次序),如有可能尽量选择时间和空间效率高的算法 c语言函数原型: - -``` -void proc(char *str) -``` -分析: - -比如:HaJKPnobAACPc,要小写字母前面且不要求保存顺序,可以是:anobcHJKPAACP - -1. 小写字母 a~z 的 ASCII码值是 97~122,A~Z的 ASCII码值是 65~90;0~9 的ASCII码是 48~57 -2. 两边向中间扫描,左边大写右边小写就交换;如果都小写,头指针向前知道找到大写;如果都是大写,尾指针向后找小写; - - -示例代码 - -``` -char *proc(char *str){ - char *start = str; - char *end = str; - if (str == NULL) return NULL; - while(*end != '\0') end++; - end--; - - while(start < end){ - if (*start >= 'A' && *start <= 'Z'){//大写 - if (*end >= 'a' && *end <= 'z'){ - char tmp = *start; - *start = *end; - *end = tmp; - - start++; - } - end--; - }else{//小写 - if (*end >= 'A' && *end <= 'Z'){ - end--; - } - start++; - } - } - return str; -} -``` - -#### 实现字符串转整型的函数 - -也就是实现函数atoi的功能,这道题目考查的就是对各种情况下异常处理。比如: - -以"213"分析转换成证书的过程。3+1x10+2x100,思路是:每扫描到一个字符,把之前得到的数字*10,再加上当前的数字 - -0开头,"0213" -正/负数,"-432" ,"--422","++23" -浮点数,"43.2344" -非法,"2123des" -存在空格," -32"," +432"," 234","23 432","353 "," + 321" -NULL/空串,这时候返回值0 -溢出,"32111111112222222222222222222222222222222" , 与 `INT_MAX `比较 - -如何区分正常的'0'和异常情况下返回的结果"0"? 可以通过一个全局变量 g_status 来标示,值为 kValid/kInvalid。 - - -``` -int atoi(const char *str){ - -} -``` - -详细过程也可以[参考这里](http://blog.csdn.net/v_july_v/article/details/9024123) - - -#### 删除串中指定的字符 - -删除指定的字符以后,后面的字符都要向前移动一位。这种复杂度是O(N^2);那么有没有O(N)的方法呢? - -比如 "abcdeccba" 删除字符 "c"。使用2个指针,一前一后,比较前面的指针和删除字符: - -1. 不相等,两个指针一起跑,且前面的指针值拷贝到后面指针指向的空间 -2. 相等时,快指针向前一步 - -``` -char *delete_occurence_character(char *src , char target){ - char *front = src; - char *rear = src; - while(*front != '\0'){ - if (*front != target){ - *rear = *front; - rear++; - } - front++; - } - *rear = '\0'; - return src; -} -``` - - -#### 在字符串中删除特定的字符 - -题目:输入两个字符串,从第一字符串中删除第二个字符串中所有的字符。例如,输入”They are students.”和”aeiou”,则删除之后的第一个字符串变成”Thy r stdnts.”。 - -这是上个题目的升级版本。 - -``` -char *delete_occurence_characterset(char *source,const char *del); -``` - -1. 蛮力法。 遍历字符串,每个字符去删除字符串集合中查找,有就删除 -2. 使用上面的方式,一次遍历 - - - -#### 删除字符串中的数字并压缩字符串 - -如字符串”abc123de4fg56”处理后变为”abcdefg”。注意空间和效率。(下面的算法只需要一次遍历,不需要开辟新空间,时间复杂度为O(N)) -这道题跟上一道题也是一个意思。 - - -示例代码: - -``` -char *trim_number(char *source){ - char *start = source; - char *end = source; - if (source == NULL) return NULL; - while(*end != '\0'){ - if (*end < '0' || *end > '9' ){ - *start = *end; - start++; - } - end++; - } - *start = '\0'; - return source; -} -``` - - - -#### 字符串原地压缩 - -题目描述:“abeeeeegaaaffa" 压缩为 "abe5ag3f2a",请编程实现。 - -这道题需要注意: -1. 单个字符不压缩 -2. 注意考虑压缩后某个字符个数是多位数(超过10个) -3. 原地压缩最麻烦的地方就是数据移动 - - -这是使用2个指针,一前一后,如果不相等,都往前移动一位;如果相等,后一位变为数字2,且移动后面的指针一位,任然相等则数字加1,不相等 - - -``` -char *compress(const char *src,char *dest){ - -} -``` - -上面的压缩算法可以看到,压缩算法的`效率`验证依赖`给定字符串的特性`,如果'aaaaaaaa....aaa' 这样特征的字符串,使用上面的压缩算法,压缩率接近100%,相反,可能会的0%的压缩率。 - - - - -#### 字符串中找出连续最长的数字串 - -写一个函数,功能: - -在字符串中找出连续最长的数字串,并把这个串的长度返回,并把这个最长数字串赋值给其中一个函数参数outputstr所指内存。 - -例如:"abcd12345ed125ss123456789"的首地址传给intputstr后,函数将返回9,outputstr所指的值为123456789 - -它的原形是: - -``` -int longest_continuious_number(const char *input,char *output) -``` - -应该有3个指针,第一个指针指向一个当前最长数字串的第一个数字,第二个指针指向第二个数字串的第一个数字,第三个指针是遍历指针,且统计第二个数字串的长度;当统计出来的长度大于第一个数字串的长度,第一个指针指向第二个指针指向的数字,相反,第二个指针和第三个指针继续向后查找。 - - -1. 当end首次碰到数字时,且tmp=0,说明是首次出现数字,第二个指针移到该数字,继续遍历 -2. 如果数字后面还是数字,tmp!=0 就是第二个数字串,因此 tmp += 1; -3. 当end从数字到普通字符时,如果tmp > max ,就要修改max和第一个指针start ,并把tmp归为0 - - -``` -int longest_continuious_number(const char *input,char *output){ - int max = 0; - char *start= input; - char *mid = input; - char *end = input; - int tmp = 0; - if (input == NULL || output == NULL) return 0; - - while (*end != '\0'){ - if (*end < '0' || *end < '9'){//字母 - if(tmp > max){ - max = tmp; - start = mid; - } - tmp = 0; - }else{//数字 - if (tmp == 0){//发现数字 - mid = end; - } - tmp++; - } - end++; - } - - //修改已数字结尾的bug - if(tmp > max){ - max = tmp; - start = mid; - } - - //copy - int i=0; - while(i strstr(str1,str2) 判断str2是否是str1的子串。 - -``` -/* -@ret 有就返回第一次出现子串的地址,否则返回NULL -*/ -char *strstr(const char *source, const char *target){ - -} -``` - - -#### 匹配兄弟字符串 - -如果两个字符串的字符一样,但是顺序不一样,被认为是兄弟字符串,问如何在迅速匹配兄弟字符串(如,bad和adb就是兄弟字符串)。 - -思路:判断各自素数乘积是否相等。更多方法请参见:http://blog.csdn.net/v_JULY_v/article/details/6347454。 - -``` -int isBrother(const char *first,const char *secd) -``` - -思路一: 循环匹配 指数级复杂度 -思路二: 利用质数,平方和比较,但这样必须是2个串的长度要一样,需要的空间比较大,最多256个字节。 - - - -示例代码: - -``` -int isBrother(const char *first,const char *secd){ - -} -``` - - - -#### 字符串的排列 - -题目:输入一个字符串,打印出该字符串中字符的所有排列。例如输入字符串abc,则输出由字符a、b、c所能排列出来的所有字符串abc、acb、bac、bca、cab和cba。输入字符串 abcca,则输出由 a,b,c排列出来的所有字符串,字符出现个数不变 - - -分析:这是一道很好的考查对递归理解的编程题 - -简单的回溯就可以实现了。当然排列的产生也有很多种算法,去看看组合数学,还有逆序生成排列和一些不需要递归生成排列的方法。 - ->印象中Knuth的第一卷里面深入讲了排列的生成。这些算法的理解需要一定的数学功底,也需要一定的灵感,有兴趣最好看看。 - - - -#### n个字符串联接 - -有n个长为m+1的字符串,如果某个字符串的最后m个字符与某个字符串的前m个字符匹配,则两个字符串可以联接,问这n个字符串最多可以连成一个多长的字符串,如果出现循环,则返回错误。 - - - - -#### 字符串的集合合并 - -给定一个字符串的集合,格式如:{aaa bbb ccc}, {bbb ddd},{eee fff},{ggg},{ddd hhh}要求将其中交集不为空的集合合并,要求合并完成后的集合之间无交集,例如上例应输出{aaa bbb ccc ddd hhh},{eee fff}, {ggg}。 - - - - -#### 编写strcpy 函数 - -已知strcpy 函数的原型是: -``` -char *strcpy(char *strDest, const char *strSrc); -``` -其中strDest 是目的字符串,strSrc 是源字符串。不调用C++/C 的字符串库函数 - - - - - - - diff --git "a/Algorithms Job Interview/\346\225\260\345\200\274\351\227\256\351\242\230.md" "b/Algorithms Job Interview/\346\225\260\345\200\274\351\227\256\351\242\230.md" deleted file mode 100644 index 74bc07f..0000000 --- "a/Algorithms Job Interview/\346\225\260\345\200\274\351\227\256\351\242\230.md" +++ /dev/null @@ -1,297 +0,0 @@ - -这部分都是一些数学几何计算方面的问题。主要由: - -* 位运算 -* 随机数 -* 大数问题 - - -#### 求1+2+…+n - -要求不能使用乘除法、for、while、if、else、switch、case等关键字以及条件判断语句(A?B:C)。 - -分析: -1. 不能使用循环,那就用递归 -2. 递归需要终止递归的条件判断语句,这里也不能用if,想其他办法,可以使用 &&逻辑与 运算符(在n>0条件满足是,才会指向后面的递归语句 - -``` -int factorial(n){ - int sum=0; - (n>0) && sum=n+factorial(n-1) - return sum; -} -``` - - -#### 整数的二进制表示中1的个数 - -题目:输入一个整数,求该整数的二进制表达中有多少个1。例如输入10,由于其二进制表示为1010,有两个1,因此输出2。 - -分析: -这是一道很基本的考查位运算的面试题。 -解法1:一轮循环移位计数 (移位运算比除法运算效率要高,注意要考虑是负数的情况) -解法2:位运算 -解法3:num &= num-1 巧妙之处在于,对高位没有影响。不断做 `num &= num-1` 直到num=0。 - -1010 & 1001 = 1000 -1000 * 0111 = 0000 - -``` -int one_appear_count_by_binary(int num){ - int count = 0; - while(num !=0 ){ - num &= num-1; - count++; - } - return count; -} -``` - - -#### 请定义一个宏,比较两个数a、b的大小,不能使用大于、小于、if语句 - -分析: -``` -#define min(a,b) ((a)>(b)?(a):(b)) -#define MIN(A,B) ({ __typeof__(A) __a = (A); __typeof__(B) __b = (B); __a < __b ? __a : __b; }) -``` - -这里不能使用比较符号: - -``` -#define min(a,b) ((a)-(b) & (0x1<<31))?(a):(b) -``` - - -#### 把十进制数(long型)分别以二进制和十六进制形式输出,不能使用printf系列 - -分析: -``` -char *integer_to_hex(long i); //eg: 20 => 14 -char *integer_to_bin(long i); //eg: 20 => 10100 -``` - -注意,转16进制中,要判断tmp[i]是否是有符号的数 -``` -tmp[i] = tmp[i]>=0 ? tmp[i] : tmp[i]+16; -``` - - - -#### 判断一个自然数是否是某个数的平方 - -说明:当然不能使用开方运算。 - -``` -square(25) YES -square(35) NO -``` - -方法1: 从1开始遍历,显然这种方法很差 -方法2: 除数跟余数比较,除数从2开始,每一轮都跟结果比较;相等就是存在这个数,不相等就把除数++;时间复杂度为(根号n)。在一个数比较大时,效率不够好。如1194877489 =(34567)^2,需要从2开始,一直比较到34566,做3万多次除法和比较运算。跟方法1一样。。 - -方法3:二分查找 O(logn)。比如25: - -a. 先取(0+25)/2=12.5,12.5*12.5>25,因此这个数应该小于12.5 -b.(0+12)/2 = 6, 6*6>25 -c. (0+6)/2 = 3 -d. (3+6)/2 = 4.5 -e. (5+6)/2 = 5.5 - -``` -int is_powered(int num) -``` - - -#### 1024! 末尾有多少个0? - -分析: -末尾0的个数取决于2和5的个数 -能被2整除的数比能被5整除的数要多得多,因此只统计被5整除的数的个数 - - - -#### 编程实现两个正整数的除法(和取模) - -编程实现两个正整数的除法,当然不能用除法操作符。 -``` -int div(const int x, const int y) -``` - -1. 循环减被除数,减到不能再减,当除数很大,被除数小时,效率很低 -2. 位运算 - - - - - -#### 求两个或N个数的最大公约数和最小公倍数。 - - - - -#### 大整数乘法(或 大整数阶乘) - -请使用代码计算1234567891011121314151617181920*2019181716151413121110987654321 。 - -1. 注意结果可能超出长整形的最大范围 2^64-1 -2. 采用分治算法,将大整数相乘转换为小整数计算 - -规律分析:任意位数的整数相乘,最终都可以转化为2位数相乘 - - - -#### 两个数相乘,小数点后位数没有限制,请写一个高精度算法 - - - -#### 数值的整数次方 - -题目:实现函数`double Power(double base, int exponent)`,求base的exponent次方。 - -不需要考虑溢出。 - -分析:这是一道看起来很简单的问题。可能有不少的人在看到题目后30秒写出如下的代码: -``` -double Power(double base, int exponent) -{ - double result = 1.0; - for(int i = 1; i <= exponent; ++i) - result *= base; - return result; -} -``` - - -上面的代码没有考虑: exponent<=0 -判断一个浮点数是不是等于0时,不是直接写 base == 0 ,而应该判断它们的差的绝对值是不是小于一个很小的范围 - -如果指数大于0,我们还可以使用递归实现:a^n = a^(n/2)*a^(n/2) (n为偶数), 通过这个思路也可以实现 -``` -2^16 = 2^8 * 2^8 -2^8 = 2^4 * 2^4 -2^4 = 2^2 * 2^2 -2^2 = 2^1 * 2^1 -2^1 = 2 -``` - -用例: -``` -2 3 = 8 -0 3 = 0 -2 0 = 1 -2 -3 = 1/(2^3) = 0.125 -0 -3 -``` - -使用递归实现的代码示例: - -``` -double Power(double base, unsigned int exponent) -{ - if (exponent == 0) return 1; - if (exponent == 1) return base; - double result = Power(base, exponent >> 1); - result *= result; - - if (exponent & 1) result = result*base; - - return reslut; -} -``` - - -#### 求根号2的值 - -并且按照我的需要列出指定小数位,比如根号2是1.141 我要列出1位小数就是1.1 2位就是1.14, 1000位就是1.141...... 等。。 - -分析: -泰勒级数 -牛顿迭代法 - - - -#### 整数的素数和分解问题 - -歌德巴赫猜想说任何一个不小于6的偶数都可以分解为两个奇素数之和。 -对此问题扩展,如果一个整数能够表示成两个或多个素数之和,则得到一个素数和分解式。 - -对于一个给定的整数,输出所有这种素数和分解式。 -注意,对于同构的分解只输出一次(比如5只有一个分解2 + 3,而3 + 2是2 + 3的同构分解式 - - -例如,对于整数8,可以作为如下三种分解: - -``` -(1) 8 = 2 + 2 + 2 + 2 -(2) 8 = 2 + 3 + 3 -(3) 8 = 3 + 5 -``` - - -#### 大于K的最小正整数 - -给定一个集合A=[0,1,3,8](该集合中的元素都是在0,9之间的数字,但未必全部包含),指定任意一个正整数K,请用A中的元素组成一个大于K的最小正整数。 - -比如,A=[1,0] K=21 那么输出结构应该为100。 - - - -#### 给定能随机生成整数1到5的函数,写出能随机生成整数1到7的函数。 - - - -#### 设计一个随机算法 - -给你5个球,每个球被抽到的可能性为30、50、20、40、10,设计一个随机算法,该算法的输出结果为本次执行的结果。输出A,B,C,D,E即可。 - - -#### 构造一个随机发生器 - -已知一随机发生器,产生0的概率是p,产生1的概率是1-p,现在要你构造一个发生器, -使得它构造0和1的概率均为1/2;构造一个发生器,使得它构造1、2、3的概率均为1/3;..., -构造一个发生器,使得它构造1、2、3、...n的概率均为1/n,要求复杂度最低。 - - - - -#### 两个圆相交 - -两个圆相交,交点是A1,A2。现在过A1点做一直线与两个圆分别相交另外一点B1,B2。B1B2可以绕着A1点旋转。问在什么情况下,B1B2最长 - - - - -#### 输入四个点的坐标,求证四个点是不是一个矩形 - -关键点: -1.相邻两边斜率之积等于-1, -2.矩形边与坐标系平行的情况下,斜率无穷大不能用积判断。 -3.输入四点可能不按顺序,需要对四点排序。 - - - - -#### 排列组合问题 - - -##### 1,2,3,4,5 五个不同的数字,打印不同的排列。这就是一个无向图的遍历,把每个数字看成一个节点。 - - -##### 用1、2、2、3、4、5这六个数字,写一个main函数,打印出所有不同的排列, - -如:512234、412345等,要求:"4"不能在第三位,"3"与"5"不能相连. - -这是对上一题增加难度。但是需要 -1. 去掉 3,5之间的联通 -2. 2重复,过滤重复结果 treeset -3. 4不能在3位 - - - - - - - - - diff --git "a/Algorithms Job Interview/\346\225\260\347\273\204\346\225\260\345\210\227\351\227\256\351\242\230.md" "b/Algorithms Job Interview/\346\225\260\347\273\204\346\225\260\345\210\227\351\227\256\351\242\230.md" deleted file mode 100644 index c5e3f6b..0000000 --- "a/Algorithms Job Interview/\346\225\260\347\273\204\346\225\260\345\210\227\351\227\256\351\242\230.md" +++ /dev/null @@ -1,668 +0,0 @@ - -这部分的问题都集中在数据集合上。主要有: - -* 数组排序 -* top-k -* 子数组 -* 多个数组合并,交集 - - -解决这一类问题时,可以从以下几个方面考虑: - -* 万能的蛮力琼剧 -* 散列表空间换事件 -* 分治法,然后归并 (归并排序) -* 选择合适的数据结构可以显著提高算法效率 (堆排序求top-k) -* 对无序的数组先排序,使用二分 -* 贪心算法或动态规划 - - -#### 数组中超过出现次数超过一半的数字 - -题目:数组中有一个数字出现的次数超过了数组长度的一半,找出这个数字。 - -`+-1 计数法` - - - -#### 找数组里面重复的一个数 - -找数组里面重复的一个数,一个含n个元素的整数数组至少存在一个重复数,请编程实现,在O(n)时间内找出其中任意一个重复数。 - -1. hash算法,空间要求多 (注意数组元素要是int类型),数的大小范围和数组长度N都可以是无穷大,这里使用hash算法,空间复杂度是O(n),空间可能无法满足条件。 -2. 先排序,复杂度n(logn);然后遍历一遍就可以知道哪些数重复了。 -2. 高级解法: 将问题转化为 `判断单链表中存在环` - - -类似问题:找出数组中唯一的重复元素 - - -#### 数组中找出某数字出现的次数 - -在排序数组中,找出给定数字的出现次数。比如 [1, 2, 2, 2, 3] 中2的出现次数是3次。 - -分析: -1. 因为是排序数组,可以使用二分查找 -2. 将二分查找坚持到底,这样在最坏的情况下([2,2,2,2,2,2,2])都有0(lgn)复杂度 - -``` -int binary_search_first(int *a,int length,int key); -int binary_search_lash(int *a,int length,int key); -``` - - - -#### 查找最小的k个元素(top-k) - -题目:输入n个整数,输出其中最小的k个。 -例如输入1,2,3,4,5,6,7和8这8个数字,则最小的4个数字为1,2,3和4。 - -``` -topMinK(int *a,int length,int k); -topMaxK(int *a,int length, int k); -``` - - -1. 全部排序 复杂度 NlogN , 数据了较大时,内存可能承受不住 -2. 部分排序 维护一个大小为K的数组,由大到小排序,然后遍历所有数据,每个数据跟数组中最小元素比较,如果比最小元素大,就要插入数组了,这里还有寻找插入位置,移动数组元素的cpu消耗。复杂度是N*K -3. 堆排序 。在这的K较大时(比如这道题目:2亿个整数中求最大的100万之和),上面的算法还是有很多可以改进的地方,如采用二分查找定位插入位置,移动数组元素的计算是躲不过去了。那有没有什么数据结构即能`快速查找,还能快速移动元素`呢?最好是O(1)复杂度。 - -答案就是`二叉堆`。我们可以遍历总量中的每个元素来跟二叉堆中的堆顶元素比较(堆顶元素在`小根堆`中最小值,在`大根堆`中是最大值),这样在0(1)复杂度就可以完成查找操作,揭下来需要的操作就是重新调整推结构,复杂度是O(logk),因此整个操作复杂度是 O(n*logk) - -`top-k 小的时候用 *大根堆* ,top-k 大得时候用 *小根堆*` - - - - - - -#### 最长公共子序列 (动态规划的经典题目) - -【最大/小差问题】 -求相邻元素的最大差值,有无序的实数列V[N],要求求里面大小相邻的实数的差的最大值,关键是要求线性空间和线性时间 - -如 【9,-1,-11,2】 最大差值 = 2-(-11) = 13 - -最小差 hash合并 -最大差 hash分解 -桶排序 - -`桶排序` 比快排还快,最耗空间 - - - -#### 找出和为m的2个数 - -输入一个已经按升序排序过的数组和一个数字,在数组中查找两个数,使得它们的和正好是输入的那个数字。要求时间复杂度是O(n)。如果有多对数字的和等于输入的数字,输出任意一对即可。 -例如输入数组【1、2、4、7、11、15=和数字15。由于4+11=15,因此输出4和11。 - -``` -//返回0找到,返回-1没找到 -int findaddends(int *data,int length,int sum,int *a,int *b); -``` - -分析: -数组升序排列,查找可用二分查找,时间复杂度O(logn),这样问题就变成了找其中一个加数的问题,复杂度为N*logN - -巧妙解法:数组2端向中间扫描,复杂度O(N) - - - -#### 和为m的组合 - -编程求解:输入两个整数 n 和 m,从数列1,2,3.......n 中 随意取几个数,使其和等于 m ,要求将其中所有的可能组合列出来. - -如: n=10,m=25;可能的组合有【】【】【】【】 -``` -print_sum_detials(int n , int sum) -``` - -`动态规划`(类似背包问题) - -1. 划分问题 - -2. 选择状态 - -3. 状态转移方程 - -4. 规划方程 - - - -#### 若干个数的和与M最为接近 - -给定一个实数数组,按序排列(从小到大),从数组从找出若干个数,使得这若干个数的和与M最为接近,描述一个算法,并给出算法的复杂度。 - - -#### 递减数列左移后的数组中找数 - -一个数组是由一个递减数列左移若干位形成的,比如{4,3,2,1,6,5} -是由{6,5,4,3,2,1}左移两位形成的,在这种数组中查找某一个数。 - - -1. 右移,二分查找。 找到最小的数,右移到第一个位置的时候,右移完成 O(N) -2. 直接二分查找 - - - -#### 求子数组的最大和(最大字段和) - -输入一个整形数组,数组里有正数也有负数。 -数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。 -求所有子数组的和的最大值。要求时间复杂度为O(n)。 - -例如输入的数组为`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保存最终的最大和 -``` - int maxSumOfVector(int *data,int length){ - - int max=0; - int maxendinghere = 0; - - if(data==NULL || length<=0){ - return 0; - } - for(int i=0;i < length, i++){ - - maxendinghere+=data[i]; - if(maxendinghere<0){ - maxendinghere=0; - continue; - } - - if(maxendinghere>max){ - max=maxendinghere; - } - - } - - return max; - } -``` - - - - - - -#### 给出一个洗牌算法 - -给出洗牌的一个算法,并将洗好的牌存储在一个整形数组里 - -分析:扑克牌54张【2~10,J,Q,K,A,小王,大王】 - -1)产生随机数, 随机数 rand()%54 ,rand()每次运行都一样,要改为srand(time(NULL)) -2) 遍历数组, 随机数k属于区间[i,n],然后a[i] 和 随机数 a[k] 对换 - - - -#### 重合区间最长的两个区间段 - -在一维坐标轴上有n个区间段,求重合区间最长的两个区间段 -如【-7,21】,【4,23】,【14,100】,【54,76】 - -思路一:两两比较,复杂度 N^2 -思路二:先排序+分而治之 - - - -#### 奇偶分离 - -给定一个存放整数的数组,重新排列数组使得数组左边为奇数,右边为偶数。 -要求:空间复杂度O(1),时间复杂度为O(n) -如 [4,5,2,7,5] => [5,7,5,4,2], -空间复杂度O(1),得使用 交换排序 - -插入排序思想 -快速排序思想 - - - - - -1-1000放在含有1001个元素的数组中,只有唯一的一个元素值重复,其它均只出现一次. -每个数组元素只能访问一次,设计一个算法,将它找出来;不用辅助存储空间, -能否设计一个算法实现? - -分析:难点在于 `不用辅助空间`。 - -思路一: sum(数组元素的总和)-sum(1~1000) 得到的差即为重复元素,N较大时注意总和溢出 -思路二: 异或操作(位运算) - - - - -#### 找出数组中两个只出现一次的数字 - -题目:一个整型数组里除了两个数字之外,其他的数字都出现了两次。 -请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。 - -分析:这是一道很新颖的关于位运算的面试题。 - -1) 任何一个数字异或自己都等于0,如果是只有1个只出现一次的的数字,问题就简单了。 -2) - - - - -#### 用递归的方法判断整数组a[N]是不是升序排列 - -递归 isAscend(n-1) && a[N-1]< a[N] - - - - -在一个int数组里查找这样的数,它大于等于左侧所有数,小于等于右侧所有数。 -直观想法是用两个数组a、b。a[i]、b[i]分别保存从前到i的最大的数和从后到i的最小的数, -一个解答:这需要两次遍历,然后再遍历一次原数组, -将所有data[i]>=a[i-1]&&data[i]<=b[i]的data[i]找出即可。 -给出这个解答后,面试官有要求只能用一个辅助数组,且要求少遍历一次。 - - - - -一排N(最大1M)个正整数+1递增,乱序排列,第一个不是最小的,把它换成-1, -最小数为a且未知。求第一个被-1替换掉的数原来的值,并分析算法复杂度。 - -[4,3,5,2,7,6] -题目啥意思? - - - -正整数序列Q中的每个元素都至少能被正整数a和b中的一个整除,现给定a和b,需要计算出Q中的前几项,例如,当a=3,b=5,N=6时,序列为3,5,6,9,10,12 -(1)、设计一个函数void generate(int a,int b,int N ,int * Q)计算Q的前几项 -(2)、设计测试数据来验证函数程序在各种输入下的正确性。 - -分析: -这个输出序列是要 递增排列 -思路一: 类似对2各数组merge,取min( A[i],B[j]) .复杂度O(N) -不过这里要注意,去掉 a, b的公倍数。如 3,5 都有15可以整除 - - - - -#### 扑克牌的顺子 - -从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。 -2-10为数字本身,A为1,J为11,Q为12,K为13,而大小王可以看成任意数字。 - -对这5个数(大小王看做0) -1)排序,如快排 -2)统计0的个数 -3)统计相邻元素空缺总数 - -``` -//5个是不是顺子 -isShunZi(int *a,int length) -``` - - - -#### n个骰子的点数 - -把n个骰子扔在地上,所有骰子朝上一面的点数之和为S。 -输入n,打印出S的所有可能的值出现的概率。 - -如n=1,值【1,2,3,4,5,6】每个概率是 1/6 -如n=2,值【1~12】 概率。。 - - - - -#### 把数组排成最小的数 - -题目:输入一个正整数数组,将它们连接起来排成一个数,输出能排出的所有数字中最小的一个。 -例如输入数组{32, 321},则输出这两个能排成的最小数字32132。 -请给出解决问题的算法,并证明该算法。 - - - - -#### 旋转数组中的最小元素。 - -题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 -输入一个排好序的数组的一个旋转,输出旋转数组的最小元素。 -例如数组{3, 4, 5, 1, 2}为{1, 2, 3, 4, 5}的一个旋转,该数组的最小值为1。 - -分析:这道题最直观的解法并不难。从头到尾遍历数组一次,就能找出最小的元素, -时间复杂度显然是O(N)。但这个思路没有利用输入数组的特性,我们应该能找到更好的解法。 - - - - - -#### 分割数组 - -一个int数组,里面数据无任何限制,要求求出所有这样的数a[i],其左边的数都小于等于它,右边的数都大于等于它。 -能否只用一个额外数组和少量其它空间实现。 - -`快速排序` - - - -#### 约瑟夫环问题 - -n个数字(0,1,…,n-1)形成一个圆圈,从数字0开始, -每次从这个圆圈中删除第m个数字(第一个为当前数字本身,第二个为当前数字的下一个数字)。 -当一个数字删除后,从被删除数字的下一个继续删除第m个数字。 -求出在这个圆圈中剩下的最后一个数字。 - -如 0,1,2,3,4,5 删除第2个数字 (n=6,m=2) -第一次删除:1 -第二次删除: 3 -第三次删除:5 -第四次删除:2 -因此,左后的一个数字就是 4 - -从数学上分析下规律: - - - - -#### 圆形和正方形是否相交 - -用最简单, 最快速的方法计算出下面这个圆形是否和正方形相交 -3D坐标系 原点(0.0,0.0,0.0) -圆形: -半径r = 3.0 -圆心o = (*.*, 0.0, *.*) - -正方形: -4个角坐标; -1:(*.*, 0.0, *.*) -2:(*.*, 0.0, *.*) -3:(*.*, 0.0, *.*) -4:(*.*, 0.0, *.*) - -分析: - -2个形状不相交: - - - -#### 两个序列和只差最小 - -有两个序列a,b,大小都为n,序列元素的值任意整数,无序; -要求:通过交换a,b中的元素,使[序列a元素的和]与[序列b元素的和]之间的差最小。 -例如: -``` -var a=[100,99,98,1,2, 3]; -var b=[1, 2, 3, 4,5,40]; -``` - - - -#### 重新排列使负数排在正数前面 - -一个未排序整数数组,有正负数,重新排列使负数排在正数前面,并且要求不改变原来的正负数之间相对顺序 - -比如: input: 1,7,-5,9,-12,15 ans: -5,-12,1,7,9,15 要求时间复杂度O(N),空间O(1)。(此题一直没看到令我满意的答案,一般达不到题目所要求的:时间复杂度O(N),空间O(1),且保证原来正负数之间的相对位置不变)。 - -updated:设置一个起始点j, 一个翻转点k,一个终止点L -从右侧起 -起始点在第一个出现的负数, 翻转点在起始点后第一个出现的正数,终止点在翻转点后出现的第一个负数(或结束) -如果无翻转点, 则不操作 -如果有翻转点, 则待终止点出现后, 做翻转, 即ab => ba 这样的操作 -翻转后, 负数串一定在左侧, 然后从负数串的右侧开始记录起始点, 继续往下找下一个翻转点 - -例子中的就是 - -1, 7, -5, 9, -12, 15 -第一次翻转: 1, 7, -5, -12,9, 15 => 1, -12, -5, 7, 9, 15 -第二次翻转: -5, -12, 1, 7, 9, 15 - - N维翻转空间占用为O(1)复杂度是2N;在有一个负数的情况下, 复杂度最大是2N, ;在有i个负数的情况下, 复杂度最大是2N+2i, 但是不会超过2N+N实际的复杂度在O(3N)以内 - 但从最终时间复杂度分析,此方法是否真能达到O(N)的时间复杂度,还待后续考证。感谢John_Lv,MikovChain。2012.02.25。 - -1, 7, -5, -6, 9, -12, 15(后续:此种情况未能处理) -1 7 -5 -6 -12 9 15 -1 -12 -5 -6 7 9 15 --6 -12 -5 1 7 9 15 - -更多请参考此文,程序员编程艺术第二十七章:重新排列数组(不改变相对顺序&时间O(N)&空间O(1),半年未被KO)http://blog.csdn.net/v_july_v/article/details/7329314。 - - - - -#### 求最大重叠区间大小 - -题目描述:请编写程序,找出下面“输入数据及格式”中所描述的输入数据文件中最大重叠区间的大小。 -对一个正整数 n ,如果n在数据文件中某行的两个正整数(假设为A和B)之间,即A<=n<=B或A>=n>=B ,则 n 属于该行; -如果 n 同时属于行i和j ,则i和j有重叠区间;重叠区间的大小是同时属于行i和j的整数个数。 - -例如,行(10 20)和(12 25)的重叠区间为 [12 20] ,其大小为9,行(20 10)和( 20 30 )的重叠区间大小为 1 。 - - - - -#### 四对括号可以有多少种匹配排列方式 - -比如两对括号可以有两种:()()和(()) - - - - -#### 输出1到最大的N位数 - -题目:输入数字n,按顺序输出从1最大的n位10进制数。比如输入3,则输出1、2、3一直到最大的3位数即999。 - -分析:这是一道很有意思的题目。看起来很简单,其实里面却有不少的玄机。 -输入4,输出: 1,2,3,。。9999 -输入5,输出: 1,2,3,4,...99999 - -玄机一: 整数溢出 - - - - -#### 和为n连续正数序列 - -题目:输入一个正数n,输出所有和为n连续正数序列。 - -例如输入15,由于1+2+3+4+5=4+5+6=7+8=15,所以输出3个连续序列1-5、4-6和7-8。 - -print_continuous_sequence_sum(int n) - - -思路一:枚举法。从1开始一直加到等于n,再从2开始。。一直到 n/2+1,在每一轮,都要逐步比较。复杂度O(N^2) - -思路二: a[small,big] sum[small,big]>N small往前移动,否则,big往前移动。 O(N)复杂度搞定 - - -### 找出和为N+1的2个数 - -一个整数数列,元素取值可能是1~N(N是一个较大的正整数)中的任意一个数,相同数值不会重复出现。 -设计一个算法,找出数列中符合条件的数对的个数,满足数对中两数的和等于N+1。 -复杂度最好是O(n),如果是O(n2)则不得分。 - -分析:列出所有的数对,如输入15,输出【1,14】【2,13】【3,12】。。。 - -``` -int print_sequence_sum(int n) -``` - - -#### 寻找丑数 - -题目:我们把只包含因子2、3和5的数称作丑数(Ugly Number)。 -例如6、8都是丑数,但14不是,因为它包含因子7。习惯上我们把1当做是第一个丑数。 -求按从小到大的顺序的第1500个丑数。 - -分析:这是一道在网络上广为流传的面试题,据说google曾经采用过这道题。 - -这里的因子应该不包含本身,因此这个序列应该是这样: -1,2,3,4,5,6,8,9,10,12,15,16,18,20,28.... - - -1)所有的偶数都在序列中 -2)3的倍数也在序列中 -3)5的倍数也在系列中 - - -0. 2,3,5最小公倍数是30 -1. [1,30]符合条件有22个 -2. [30,60]符合条件也22个 - -第1500个: 1500/22=68余4,一个周期内的前4个数是2,3,4,5; 最终答案是`68*30+5` - - - -#### 调整数组顺序使奇数位于偶数前面 - -题目:输入一个整数数组,调整数组中数字的顺序,使得所有奇数位于数组的前半部分, -所有偶数位于数组的后半部分。要求时间复杂度为O(n)。 - -思路:两边向中间扫描,如果第一个指针是偶数,第二个指针是奇数,就交换;如果第一个是偶数,第二个也偶数,第二个指针向前移;反之,第一个指针向后移 - -``` -void reorder(int *data, int length); -``` - - -#### 在从1到n的正数中1出现的次数 - -题目:输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数。 - -例如输入12,从1到12这些整数中包含1 的数字有1,10,11和12,1一共出现了5次。 -分析:这是一道广为流传的google面试题。 -``` -int one_appear_count(int n); -``` - -思路1. 遍历1~n,统计出现1的个数;n足够大时,效率很低 -思路2. 分析规律 - - - -#### 求一个数组的最长递减子序列 - - 比如{9,4,3,2,5,4,3,2}的最长递减子序列为{9,5,4,3,2} - -`动态规划` - - - -#### 如何求2个集合的交集 - -1.每个集合里面是否有重复元素? - -思路一:hash,复杂度O(M+N) - - -#### 两两之差绝对值最小的值 - -有一个整数数组,请求出两两之差绝对值最小的值,记住,只要得出最小值即可,不需要求出是哪两个数。 -如 [-1, 3, 5, 9] 绝对值最小的是2(5-3) - -最短区间问题 -如果是有重复元素,那最小值就是0了 - -解题思路: 可以将这个问题转化为 ”求最大字段和“ 问题。。。 - - - -#### 数值是否连续相邻 - -一个整数数列,元素取值可能是0~65535中的任意一个数,相同数值不会重复出现。0是例外,可以反复出现。请设计一个算法,当你从该数列中随意选取5个数值,判断这5个数值是否连续相邻。 -注意: -- 5个数值允许是乱序的。比如: 8 7 5 0 6 -- 0可以通配任意数值。比如:8 7 5 0 6 中的0可以通配成9或者4 -- 0可以多次出现。 -- 复杂度如果是O(n2)则不得分。 - -这个问题跟”扑克牌顺子“判断问题一样,通过比较0的个数和相邻数字之间间隔总和来判断所有数是否连续。 - - - - -#### 最长递增子序列 - -题目描述:设L=是n个不同的实数的序列,L的递增子序列是这样一个子序列 -Lin=< aK1,ak2,…,akm >,其中k1< k2<…< km且aK1< ak2<…< akm。 -求最大的m值。 - -如【5,6,7,3,2,8】 最长子序列 【5,6,7,8】 - -`动态规划` - - - - -#### Fibonacci数列 - -题目:定义Fibonacci数列如下: - / 0 n=0 -f(n)= 1 n=1 - / f(n-1)+f(n-2) n=2 - -输入n,用最快的方法求该数列的第n项。 - -虽然fibonacci数列是`递归`的经典应用,但递归效率很差,会有很多重复的计算,复杂度是成指数递增的,我测试了下计算50的时候已经要300s了。 - -思路二:从下往上计算,复杂度O(N),一个循环就搞定 - -思路三: logN - - - - - -#### 一个整数数组,长度为n,将其分为m份,使各份的和相等,求m的最大值 - - 比如{3,2,4,3,6} 可以分成{3,2,4,3,6} m=1; - {3,6}{2,4,3} m=2 - {3,3}{2,4}{6} m=3 所以m的最大值为3 - - - - -////////////// 数列 /////////////// - -输入a1,a2,...,an,b1,b2,...,bn, -在O(n)的时间,O(1)的空间将这个序列顺序改为a1,b1,a2,b2,a3,b3,...,an,bn, -且不需要移动,通过交换完成,只需一个交换空间。 - -例如,N=9时,第2步执行后,实际上中间位置的两边对称的4个元素基本配对, -只需交换中间的两个元素即可,如下表所示。颜色表示每次要交换的元素,左边向右交换,右边向左交换。 -交换过程如下表所示 -交换x1,x3;交换x2,x4;再交换中间的x1,x4;交换y1,y2。 - - - - -给你10分钟时间,根据上排给出十个数,在其下排填出对应的十个数 -要求下排每个数都是先前上排那十个数在下排出现的次数。 -上排的十个数如下: - -【0,1,2,3,4,5,6,7,8,9】 - -初看此题,貌似很难,10分钟过去了,可能有的人,题目都还没看懂。 - -举一个例子, -数值: 0,1,2,3,4,5,6,7,8,9 -分配: 6,2,1,0,0,0,1,0,0,0 -0在下排出现了6次,1在下排出现了2次, -2在下排出现了1次,3在下排出现了0次.... -以此类推.. - -0xX0+1xX1+2xX2....+9xX9 = 10; -X0+X1+...+ = 10; - -转换成多元一次方程求通解的问题。(Matlib) - - - - -给出两个集合A和B,其中集合A={name}, -集合B={age、sex、scholarship、address、...}, -要求: -问题1、根据集合A中的name查询出集合B中对应的属性信息; -问题2、根据集合B中的属性信息(单个属性,如age<20等),查询出集合A中对应的name。 - - - - - - diff --git "a/Algorithms Job Interview/\351\223\276\350\241\250.md" "b/Algorithms Job Interview/\351\223\276\350\241\250.md" deleted file mode 100644 index a521e95..0000000 --- "a/Algorithms Job Interview/\351\223\276\350\241\250.md" +++ /dev/null @@ -1,193 +0,0 @@ - -链表常常碰到的问题有: - -* 链表反转 -* 链表中是否有环 -* 删除链表中的p节点,在p节点前面插入节点q, 要求O(1)复杂度 -* 2个链表相交 -* 2个链表合并 - - -#### 判断俩个链表是否相交 - -给出俩个单向链表的头指针,比如h1,h2,判断这俩个链表是否相交。为了简化问题,我们假设俩个链表均不带环。 - -问题扩展: - -1.如果链表可能有环列? -2.如果需要求出俩个链表相交的第一个节点列? - - -#### 输出两个非降序链表的并集 - -请修改append函数,利用这个函数实现: - -两个非降序链表的并集,1->2->3 和 2->3->5 并为 1->2->3->5 。另外只能输出结果,不能修改两个链表的数据。 - - -#### 输出链表中倒数第k个结点 - -题目:输入一个单向链表,输出该链表中倒数第k个结点。链表的倒数第0个结点为链表的尾指针。 -链表结点定义如下: -struct ListNode -{ - int m_nKey; - ListNode* m_pNext; -}; - - -#### 链表排序 - -Given a head pointer pointing to a linked list ,please write a function that sort the list -in increasing order. You are not allowed to use temporary array or memory copy (微软面试题) - -``` -struct -{ - int data; - struct S_Node *next; -}Node; - -Node * sort_link_list_increasing_order (Node *pheader): -``` - - -#### 给定单链表,检测是否有环。 - -使用两个指针p1,p2从链表头开始遍历,p1每次前进一步,p2每次前进两步。如果p2到达链表尾部,说明无环,否则p1、p2必然会在某个时刻相遇(p1==p2),从而检测到链表中有环。 - - -类似的题有: - -1 给定两个单链表(head1, head2),检测两个链表是否有交点,如果有返回第一个交点。 - - 如果head1==head2,那么显然相交,直接返回head1。 - 否则,分别从head1,head2开始遍历两个链表获得其长度len1与len2,假设len1>=len2, -那么指针p1由head1开始向后移动len1-len2步,指针p2=head2, -下面p1、p2每次向后前进一步并比较p1p2是否相等,如果相等即返回该结点, -否则说明两个链表没有交点。 - - -2 给定单链表(head),如果有环的话请返回从头结点进入环的第一个节点。 - - 运用题一,我们可以检查链表中是否有环。如果有环,那么p1p2重合点p必然在环中。从p点断开环,方法为:p1=p, p2=p->next, -p->next=NULL。此时,原单链表可以看作两条单链表,一条从head开始,另一条从p2开始, -于是运用题二的方法,我们找到它们的第一个交点即为所求。 - - -#### 删除链表中的p节点 - -只给定单链表中某个结点p(并非最后一个结点,即p->next!=NULL)指针,删除该结点。 - -办法很简单,首先是放p中数据,然后将p->next的数据copy入p中,接下来删除p->next即可。 - - -类似的还有问题:只给定单链表中某个结点p(非空结点),在p前面插入一个结点。办法类似,首先分配一个结点q,将q插入在p后, -接下来将p中的数据copy入q中,然后再将要插入的数据记录在p中。都可以做到0(1)复杂度 - - -#### 链表反转 - -三个指针,遍历一遍(0(n)复杂度 - - - -#### 复杂链表的复制 - -题目:有一个复杂链表,其结点除了有一个m_pNext指针指向下一个结点外, -还有一个m_pSibling指向链表中的任一结点或者NULL。其结点的C++定义如下: -``` - struct ComplexNode -{ - int m_nValue; - ComplexNode* m_pNext; - ComplexNode* m_pSibling; -}; -``` - -下图是一个含有5个结点的该类型复杂链表。图中实线箭头表示m_pNext指针,虚线箭头表示m_pSibling指针。为简单起见,指向NULL的指针没有画出。 -请完成函数`ComplexNode* Clone(ComplexNode* pHead)`,以复制一个复杂链表。 - - - -#### 找出两个链表的第一个公共结点 - -题目:两个单向链表,找出它们的第一个公共结点。链表的结点定义为: -``` -struct ListNode -{ - int m_nKey; - ListNode* m_pNext; -}; -``` - -分析: -第一个公共节点,也就是2个链表中的节点的m_pNext 指向的同一个节点。 - -1. 先遍历2个链表,得到各自的长度,和差sub -2. 长链表先遍历sub个节点,然后2个节点一起遍历 -3. 第一次同时指向的同一个节点就是这个commonNode - - - - -#### 从尾到头输出链表 - -输入一个链表的头结点,从尾到头反过来输出每个结点的值。链表结点定义如下: - -``` -struct ListNode{ - int m_nKey; - ListNode* m_pNext; -}; -``` - -思路一: 辅助栈,需要一个栈空间 -思路二: 反转链表,然后遍历 -思路三: 递归实现,将printf语句放在递归调用后面。果然妙极。。 - - -#### 在O(1)时间内删除链表结点 -题目:给定链表的头指针和一个结点指针,在O(1)时间删除该结点。链表结点的定义如下: - -``` -struct ListNode{ - int m_nKey; - ListNode* m_pNext; -}; - -//函数的声明如下: -void DeleteNode(ListNode* pListHead, ListNode* pToBeDeleted); - -``` - -思路: -保存下一个节点的值tmp,删除下一个节点,当前节点=tmp - - -#### 将两链表中data值相同的结点删除 - - -有双向循环链表结点定义为: - -``` -struct node{ - int data; - struct node *front,*next; -}; - -```` - -有两个双向循环链表A,B,知道其头指针为:pHeadA,pHeadB,请写一函数将两链表中data值相同的结点删除。 - - - - -#### 链表和数组的区别在哪里? - -分析:主要在基本概念上的理解。 -但是最好能考虑的全面一点,现在公司招人的竞争可能就在细节上产生, -谁比较仔细,谁获胜的机会就大。 - - - diff --git "a/Big Data/Bloom filter(\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250).md" "b/Big Data/Bloom filter(\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250).md" deleted file mode 100644 index 250e642..0000000 --- "a/Big Data/Bloom filter(\345\270\203\351\232\206\350\277\207\346\273\244\345\231\250).md" +++ /dev/null @@ -1,89 +0,0 @@ - -## Bloom filter(布隆过滤器) - -Bloom Filter是由Bloom在1970年提出的一种多哈希函数映射的快速查找算法。通常应用在一些需要快速判断某个元素是否属于集合,但是并不严格要求100%正确的场合。 - - -### Bloom filter 特点 - -为了说明Bloom Filter存在的重要意义,举一个实例:假设要你写一个网络蜘蛛(web crawler)。由于网络间的链接错综复杂,蜘蛛在网络间爬行很可能会形成“环”。为了避免形成“环”,就需要知道蜘蛛已经访问过那些URL。给一个URL,怎样知道蜘蛛是否已经访问过呢?稍微想想,就会有如下几种方案: - -1. 将访问过的URL保存到数据库。 -2. 用HashSet将访问过的URL保存起来。那只需接近O(1)的代价就可以查到一个URL是否被访问过了。 -3. URL经过MD5或SHA-1等单向哈希后再保存到HashSet或数据库。 -4. BitMap方法。建立一个BitSet,将每个URL经过一个哈希函数映射到某一位。 - -方法1~3都是将访问过的URL完整保存,方法4则只标记URL的一个映射位。以上方法在数据量较小的情况下都能完美解决问题,但是当数据量变得非常庞大时问题就来了。 - -方法1的缺点:数据量变得非常庞大后关系型数据库查询的效率会变得很低。而且每来一个URL就启动一次数据库查询是不是太小题大做了? -方法2的缺点:太消耗内存。随着URL的增多,占用的内存会越来越多。就算只有1亿个URL,每个URL只算50个字符,就需要5GB内存。 -方法3:由于字符串经过MD5处理后的信息摘要长度只有128Bit,SHA-1处理后也只有160Bit,因此方法3比方法2节省了好几倍的内存。 -方法4消耗内存是相对较少的,但缺点是单一哈希函数发生冲突的概率太高。还记得数据结构课上学过的Hash表冲突的各种解决方法么?若要降低冲突发生的概率到1%,就要将BitSet的长度设置为URL个数的100倍。 - -实质上上面的算法都忽略了一个重要的隐含条件:允许小概率的出错,不一定要100%准确!也就是说少量url实际上没有没网络蜘蛛访问,而将它们错判为已访问的代价是很小的——大不了少抓几个网页呗。 - - -### Bloom filter 算法 - -Bloom filter可以看做是对bitmap的扩展。只是使用多个hash映射函数,从而减低hash发生冲突的概率。算法如下: - -1. 创建 m 位的bitset,初始化为0, 选中k个不同的哈希函数 -2. 第 i 个hash 函数对字符串str 哈希的结果记为 h(i,str) ,范围是(0,m-1) -3. 将字符串记录到bitset的过程:对于一个字符串str,分别记录h(1,str),h(2,str)...,h(k,str)。 然后将bitset的h(1,str),h(2,str)...,h(k,str)位置1。也就是将一个str映射到bitset的 k 个二进制位。 - -4. 检查字符串是否存在:对于字符串str,分别计算h(1,str)、h(2,str),...,h(k,str)。然后检查BitSet的第h(1,str)、h(2,str),...,h(k,str) 位是否为1,若其中任何一位不为1则可以判定str一定没有被记录过。若全部位都是1,则“认为”字符串str存在。但是若一个字符串对应的Bit全为1,实际上是不能100%的肯定该字符串被Bloom Filter记录过的。(因为有可能该字符串的所有位都刚好是被其他字符串所对应)这种将该字符串划分错的情况,称为false positive 。 - -5. 删除字符串:字符串加入了就被不能删除了,因为删除会影响到其他字符串。实在需要删除字符串的可以使用Counting bloomfilter(CBF)。 - - -`Bloom Filter 使用了k个哈希函数,每个字符串跟k个bit对应。从而降低了冲突的概率。` - - - -### 最优的哈希函数个数,位数组m大小 - -哈希函数的选择对性能的影响应该是很大的,一个好的哈希函数要能近似等概率的将字符串映射到各个Bit。选择k个不同的哈希函数比较麻烦,一种简单的方法是选择一个哈希函数,然后送入k个不同的参数。 - -在原始个数位n时,那这里的k应该取多少呢?位数组m大小应该取多少呢?这里有个计算公式:`k=(ln2)*(m/n)`, 当满足这个条件时,错误率最小。 - - -假设错误率为0.01, 此时m 大概是 n 的13倍,k大概是8个。 这里的n是元素记录的个数,m是bit位个数。如果每个元素的长度原大于13,使用Bloom Filter就可以节省内存。 - - -### 错误率估计 - - - -### 实现示例 - -``` -#define SIZE 15*1024*1024 -char a[SIZE]; /* 15MB*8 = 120M bit空间 */ -memset(a,0,SIZE); - -int seeds[] = { 5, 7, 11, 13, 31, 37, 61}; - -int hashcode(int cap,int seed, string key){ - int hash = 0; - for (int i=0;i数学是科学的皇后 ->数学是对自然界事实的总结和归纳,又是抽象思考的结果 ->数学是上帝描写自然的语言 —— 伽利略 - - -## 学习方法 - -* 刷题,练习 -* 避免纯数学性推导,先弄清楚每门学问要解决的问题是什么,理清知识脉络 -* 可汗学院,网易公开课,知乎,google - - -## 初等数学 - -也就是大学以前学习的数学内容。包括代数,几何,集合论等。 - -初等数学看重的是应用能力,很多定理都是靠记忆;比如很多定理,你记住它然后会懂得算就OK了。高等数学看重的是翻译能力,这个翻译指的是把逻辑翻译成数学语言。 - - - -## 微积分 - -微积分起源于资本主义工业革命,工业的发展需要精确计算各种运动如机械运动,天体运动,流体与气体运动的规律性。许多问题需要解决,归纳有下: - -1. 求即时速度 -2. 求曲线的切线问题 -3. 求函数的最大值和最小值问题 -4. 求曲线长,曲线围成的面积,曲面围成的体积,物体的重心 - - -微积分研究的对象是变量(或函数)。`极限理论`是微积分的基础。微积分包括`微分` 和 `积分` ,微分和积分互为逆运算。 - -* **微分**: 内容有 `极限理论` 、 `导数` 、`微分`, 是一套关于关于变化率的理论,将函数,速度,加速度,曲线,曲率用一套通用符号表示。 -* **积分**: 内容有`定积分 ` 和 `不定积分` , 是计算面积和体积的一套理论方法。 -* **极限理论** -* **一元微积分** -* **多元微积分** -* **空间解析几何** -* **级数理论** -* **常微分方程** - - -#### 参考 - -《常微分方程》 也是要刷题的 -《网易公开课》 - - - - -## 线性代数 - -`线性代数`是数学的另一个分支,研究对象是`向量` `矩阵` `线性变换` `线性方程组` - - -* 矩阵 -* 线性方程组 -* 向量 -* 秩与维数 -* 正交化 -* 特征值与特征向量 - - -### 参考 - -《线性代数》 李尚志 -《线性代数及其应用》 David C. Lay -[《网易公开课》](http://c.open.163.com/search/search.htm?query=%E7%BA%BF%E6%80%A7%E4%BB%A3%E6%95%B0#/search/all) - - - -## 概率论与数理统计 - - -* 随机变量 -* 概率分布 -* 随机变量的数字特征 -* 参数估计 -* 假设检验 -* 回归分析 - - - -### 应用领域 - -* 实现winzip -* 基于全概率公式的贝叶斯分类算法,数据挖掘,自然语言处理 -* 垃圾邮件过滤 -* 打tag -* 网络性能分析,评估服务质量 - - -更多应用可以看看[知乎这里的讨论](http://www.zhihu.com/question/25047877) - -### 参考 - -《概率论与数理统计》 陈希儒 - - - -## 离散数学 - - ->一切算法的基础都是离散数学 ->一切加密的理论基础都是离散数学 ->编程时候很多奇怪的小技巧(特别是所有和位计算相关的东西)核心也是离散数学 -> from 知乎网友:http://www.zhihu.com/question/38040913 - -离散数学可以看做是数学和计算机之间的桥梁。计算机只懂得离散。 - -离散数学是计算机算法的基础 - -离散数学最重要的两个内容:`数论`和`图论`。 - - -* 集合论 -* 数理逻辑 -* 图论 - - - -#### 应用领域 - -算法 -语言信息处理 -数据挖掘和机器学习 - - - - -## 推荐阅读 - -《统计学习方法》 李航 -《高等数学·上下册》 -《概率论与数理统计·浙大版》、《数理统计学简史·陈希孺》 -《概率论与数理统计》 陈希儒 -《线性代数》 李尚志 -《线性代数及其应用》 David C. Lay -《矩阵分析与应用·张贤达》 -《凸优化(Convex Optimization) · Stephen Boyd & Lieven Vandenberghe著》 -《Pattern Recognition And Machine Learning · Christopher M. Bishop著》,简称PRML - - -《数学之美》 吴军 -《思考的乐趣》《Matrix67数学笔记》 顾森 ,网名[Matrix67](http://www.matrix67.com/blog/) - - - diff --git a/README.md b/README.md index 57832b0..6663323 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - +#The file is in Chinese >算法虐我千百遍,我待算法如初恋 @@ -8,7 +8,7 @@ * 把所有经典算法写一遍 * 看算法有关源码 -* 加入算法学习社区,相互鼓励学习 +* 加入算法学习社区,相互鼓励学习(加我vx:tiger-ran, 备注入群理由, 拉你入群) * 看经典书籍 * 刷题 @@ -17,46 +17,56 @@ 这些算法全部自己敲一遍: -### 链表 +### [链表](2%20List/README.md) * 链表 * 双向链表 + +### [数组](2%20List/数组.md) + +* [数组数列问题](9%20Algorithms%20Job%20Interview/5%20数组数列问题.md) + +数组和链表结构是基础结构,散列表、栈、队列、堆、树、图等等各种数据结构都基于数组和链表结构实现。 + +### [队列](2%20Queue/README.md) -### 哈希表/散列表 (Hash Table) +* 队列 +* 堆栈 + +### [哈希表 HashTable](3%20Hash%20Table/README.md) * 散列函数 * 碰撞解决 -### 字符串算法 +### [字符串算法](1%20String/README.md) -* 排序 -* 查找 +* 子串查找 [字符串常见题目参考这里](9%20Algorithms%20Job%20Interview/1%20字符串.md) * BF算法 * KMP算法 * BM算法 * 正则表达式 * 数据压缩 +* 排序 + +### [树](4%20Tree/README.md) -### 二叉树 - -* 二叉树 -* 二叉查找树 -* 伸展树(splay tree 分裂树) -* 平衡二叉树AVL -* 红黑树 -* B树,B+,B* -* R树 -* Trie树(前缀树) -* 后缀树 -* 最优二叉树(赫夫曼树) -* 二叉堆 (大根堆,小根堆) -* 二项树 -* 二项堆 +* 二叉树 [快速排序](6%20Sort/README.md)就是个二叉树的前序遍历,归并排序就是个二叉树的后序遍历 +* [二叉查找树BST](4%20Tree/2-二叉查找树/二叉查找树.md) 有序的二叉树,中序遍历结果是递增的 +* [平衡二叉树 AVL树](4%20Tree/3-平衡树AVL/README.md) 绝对平衡二叉树; +* [红黑树](4%20Tree/9-红黑树%20R-B%20tree/红黑树.md) 弱平衡二叉树;使用广泛 +* [B树](4%20Tree/7-B树/B树.md) +* [B+树](4%20Tree/7-B树/B+树.md) mysql 索引使用 B+树 的数据结构 +* [字典树trie](4%20Tree/4-字典树Trie/README.md) 字典树也叫前缀树,单词查找树 +* [二叉堆](4%20Tree/8-堆/堆.md) +* [伸展树](4%20Tree/5-伸展树/伸展树.md) +* [后缀树](4%20Tree/6-后缀树/后缀树.md) * 斐波那契堆(Fibonacci Heap) +* 最优二叉树(赫夫曼树) -### 图的算法 + +### [图的算法](5%20Graph/README.md) * 图的存储结构和基本操作(建立,遍历,删除节点,添加节点) * 最小生成树 @@ -66,7 +76,7 @@ -### 排序算法 +### [排序算法](6%20Sort/README.md) **交换排序算法** @@ -83,60 +93,31 @@ * 桶排序 -### 查找算法 +### [查找算法](7%20Search/README.md) + -* 顺序表查找:顺序查找 -* 有序表查找:二分查找 +* 哈希表: O(1) [hashtable实现参考这里](../3%20Hash%20Table/README.md) +* 有序表查找:二分查找 +* 顺序表查找:顺序查找, 复杂度O(N) * 分块查找: 块内无序,块之间有序;可以先二分查找定位到块,然后再到`块`中顺序查找 -* 动态查找: 二叉排序树,AVL树,B- ,B+ (这里之所以叫 `动态查找表`,是因为表结构是查找的过程中动态生成的) -* 哈希表: O(1) - - -### 15个经典基础算法 - -* Hash -* 快速排序 -* 快递选择SELECT -* BFS/DFS (广度/深度优先遍历) -* 红黑树 (一种自平衡的`二叉查找树`) -* KMP 字符串匹配算法 -* DP (动态规划 dynamic programming) -* A*寻路算法: 求解最短路径 -* Dijkstra:最短路径算法 (八卦下:Dijkstra是荷兰的计算机科学家,提出”信号量和PV原语“,"解决哲学家就餐问题",”死锁“也是它提出来的) -* 遗传算法 -* 启发式搜索 -* 图像特征提取之SIFT算法 -* 傅立叶变换 -* SPFA(shortest path faster algorithm) 单元最短路径算法 - +* 动态查找: 二叉排序树,AVL树,B- ,B+(这里之所以叫 `动态查找表`,是因为表结构是查找的过程中动态生成的) + -## 海量数据处理 +## [算法设计思想](8%20Algorithms%20Analysis/README.md) -* Hash映射/分而治之 -* Bitmap -* Bloom filter(布隆过滤器) -* Trie树 -* 数据库索引 -* 倒排索引(Inverted Index) -* 双层桶划分 -* 外排序 -* simhash算法 -* 分布处理之Mapreduce +* [递归](8%20Algorithms%20Analysis/递归.md) +* [分治算法](8%20Algorithms%20Analysis/分治算法.md) +* [动态规划](8%20Algorithms%20Analysis/动态规划.md) +* [回溯法](8%20Algorithms%20Analysis/回溯法.md) +* [迭代法](8%20Algorithms%20Analysis/迭代法.md) +* [穷举搜索法](8%20Algorithms%20Analysis/穷举搜索法.md) +* [贪心算法](8%20Algorithms%20Analysis/贪心算法.md) -## 算法设计思想 - -* 迭代法 -* 穷举搜索法 -* 递推法 -* 动态规划 -* 贪心算法 -* 回溯 -* 分治算法 -## 算法问题选编 +## [面试算法题目](9%20Algorithms%20Job%20Interview/README.md) 这是一个算法题目合集,题目是我从网络和书籍之中整理而来,部分题目已经做了思路整理。问题分类包括: @@ -159,68 +140,103 @@ * 剑指offer -## 开源项目中的算法 +## [海量数据处理](91%20Algorithms%20In%20Big%20Data/README.md) + +* Hash映射/分而治之 +* Bitmap +* Bloom filter(布隆过滤器) +* Trie树 +* 数据库索引 +* 倒排索引(Inverted Index) +* 双层桶划分 +* 外排序 +* simhash算法 +* 分布处理之Mapreduce + + + +## [开源项目中的算法](93%20Algorithms%20In%20Open%20Source/README.md) * YYCache * cocos2d-objc +* bitcoin +* geohash +* kafka +* nginx +* zookeeper * ... + + +## 15个经典基础算法 + + +* [KMP 字符串匹配算法](1%20String/KMP.md) +* [Hash](3%20Hash%20Table/README.md) +* [快速排序](6%20Sort/README.md) +* 快速选择SELECT +* [红黑树 (一种弱/自平衡的`二叉查找树`)](4%20Tree/9-红黑树%20R-B%20tree/红黑树.md) +* [BFS/DFS (广度/深度优先遍历)](5%20Graph/DFS%20和%20BFS.md) +* [`A*`寻路算法: 求解最短路径](5%20Graph/最短路径.md) +* Dijkstra:最短路径算法 +* `SPFA(Shortest Path Faster Algorithm)` 单元最短路径算法 +* 启发式搜索 +* 遗传算法 `GA` +* [DP (动态规划 dynamic programming)](8%20Algorithms%20Analysis/动态规划.md) +* 图像特征提取之`SIFT` 算法 , 广泛的应用于图像识别,图像检索,3D重建等CV的各种领域 +* 傅立叶变换 + + ## 推荐阅读 ### 刷题必备 -《剑指offer》 -《编程之美》 -《编程之法:面试和算法心得》   -《算法谜题》 都是思维题 +* 《剑指offer》 +* 《编程之美》 +* 《编程之法:面试和算法心得》   +* 《算法谜题》 都是思维题 ### 基础 -《编程珠玑》Programming Pearls -《编程珠玑(续)》 -《数据结构与算法分析》 -《Algorithms》 这本近千页的书只有6章,其中四章分别是排序,查找,图,字符串,足见介绍细致 +* 《编程珠玑》Programming Pearls +* 《编程珠玑(续)》 +* 《数据结构与算法分析》 +* 《Algorithms》 这本近千页的书只有6章,其中四章分别是排序,查找,图,字符串,足见介绍细致 ### 算法设计 -《算法设计与分析基础》 -《算法引论》 告诉你如何创造算法 断货 -《Algorithm Design Manual》算法设计手册 红皮书 - -《算法导论》 是一本对算法介绍比较全面的经典书籍 - -《Algorithms on Strings,Trees and Sequences》 -《Advanced Data Structures》 各种诡异高级的数据结构和算法 如元胞自动机、斐波纳契堆、线段树 600块 +* 《算法设计与分析基础》 +* 《算法引论》 告诉你如何创造算法 断货 +* 《Algorithm Design Manual》算法设计手册 红皮书 +* 《算法导论》 是一本对算法介绍比较全面的经典书籍 +* 《Algorithms on Strings,Trees and Sequences》 +* 《Advanced Data Structures》 各种诡异高级的数据结构和算法 如元胞自动机、斐波纳契堆、线段树 600块 ### 延伸阅读 -《深入理解计算机系统》 -《TCP/IP详解三卷》 -《UNIX网络编程二卷》 -《UNIX环境高级编程:第2版》 - - -《The practice of programming》 Brian Kernighan和Rob Pike -《writing efficient programs》 优化 -《The science of programming》 证明代码段的正确性 800块一本 +* 《深入理解计算机系统》 +* 《TCP/IP详解三卷》 +* 《UNIX网络编程二卷》 +* 《UNIX环境高级编程:第2版》 +* 《The practice of programming》 Brian Kernighan和Rob Pike +* 《writing efficient programs》 优化 +* 《The science of programming》 证明代码段的正确性 800块一本 ## 参考链接和学习网站 ### [July 博客](http://blog.csdn.net/v_july_v) -《数学建模十大经典算法》 -《数据挖掘领域十大经典算法》 -《十道海量数据处理面试题》 -《数字图像处理领域的二十四个经典算法》 -《精选微软等公司经典的算法面试100题》 - - -[The-Art-Of-Programming-By-July](https://github.com/julycoding/The-Art-Of-Programming-By-July) -[微软面试100题](http://blog.csdn.net/column/details/ms100.html) -[程序员编程艺术](http://blog.csdn.net/v_JULY_v/article/details/6460494) +* 《数学建模十大经典算法》 +* 《数据挖掘领域十大经典算法》 +* 《十道海量数据处理面试题》 +* 《数字图像处理领域的二十四个经典算法》 +* 《精选微软等公司经典的算法面试100题》 +* [The-Art-Of-Programming-By-July](https://github.com/julycoding/The-Art-Of-Programming-By-July) +* [微软面试100题](http://blog.csdn.net/column/details/ms100.html) +* [程序员编程艺术](http://blog.csdn.net/v_JULY_v/article/details/6460494) ### 基本算法演示 @@ -231,13 +247,14 @@ http://www.cs.usfca.edu/~galles/visualization/Algorithms.html ### 编程网站 -[leetcode](http://leetcode.com/) -[openjudge](http://openjudge.cn/) 开放在线程序评测平台,可以创建自己的OJ小组   -[九度OJ](http://ac.jobdu.com/index.php) - -这有个[ACM训练方案](http://www.java3z.com/cwbwebhome/article/article19/res041.html) +* [leetcode](http://leetcode.com/) +* [codetop](https://codetop.cc/home) 企业高频面试题库,刷题必备 +* [openjudge](http://openjudge.cn/) 开放在线程序评测平台,可以创建自己的OJ小组   +* [九度OJ](http://ac.jobdu.com/index.php) +* 这有个[ACM训练方案](http://www.java3z.com/cwbwebhome/article/article19/res041.html) -### 其它 + +### 网课 [高级数据结构和算法](https://www.coursera.org/learn/gaoji-shuju-jiegou/) 北大教授张铭老师在coursera上的课程。完成这门课之时,你将掌握多维数组、广义表、Trie树、AVL树、伸展树等高级数据结构,并结合内排序、外排序、检索、索引有关的算法,高效地解决现实生活中一些比较复杂的应用问题。当然coursera上也还有很多其它算法方面的视频课程。 @@ -245,7 +262,11 @@ http://www.cs.usfca.edu/~galles/visualization/Algorithms.html [算法设计与分析 Design and Analysis of Algorithms](https://class.coursera.org/algorithms-001/lecture) 由北大教授Wanling Qu在coursera讲授的一门算法课程。首先介绍一些与算法有关的基础知识,然后阐述经典的算法设计思想和分析技术,主要涉及的算法设计技术是:分治策略、动态规划、贪心法、回溯与分支限界等。每个视频都配有相应的讲义(pdf文件)以便阅读和复习。 +### 其它 + +[OI Wiki](https://github.com/24OI/OI-wiki/) 主要内容是 OI/ACM-ICPC 编程竞赛 (competitive programming) 相关的知识整理, 包括基础知识、常见题型、解题思路以及常用工具等内容。 +[labuladong 的算法小抄](https://labuladong.gitee.io/algo/) 作者整理了很多的解题套路框架,看完获益良多 ## 联系 diff --git a/Search Algorithms/README.md b/Search Algorithms/README.md deleted file mode 100644 index 2fcf2a5..0000000 --- a/Search Algorithms/README.md +++ /dev/null @@ -1,62 +0,0 @@ - -### 查找算法 - -* 顺序查找 -* 二分查找 -* 分块查找 -* 动态查找 -* 哈希表 - - -#### 顺序查找 - -顺序表查找。复杂度O(n) - -#### 二分查找 - -有序表中查找我们可以使用二分查找。 - -``` -/* -eg: [1,3,5,6,7,9] k=6 -@return 返回元素的索引下表,找不到就返回-1 -*/ -int binary_search(int *a,int length,int k){ - int low = 0; - int high = length-1; - int mid; - - while(low k) high = mid-1; - } - - return -1; -} -``` - -#### 分块查找 - -块内无序,块之间有序;可以先二分查找定位到块,然后再到块中顺序查找。 - - -#### 动态查找 - -这里之所以叫 动态查找表,是因为表结构是查找的过程中动态生成的。查找结构通常是二叉排序树,AVL树,B- ,B+等。这部分的内容可以去看『二叉树』章节 - -#### 哈希表 - -哈希表以复杂度O(1)的成绩位列所有查找算法之首,大量查找的数据结构中都可以看到哈希表的应用。 - - - - - - - - - - - diff --git a/Sort/insert_srot.c b/Sort/insert_srot.c deleted file mode 100644 index c4e7098..0000000 --- a/Sort/insert_srot.c +++ /dev/null @@ -1,298 +0,0 @@ - -#include "stdio.h" -#include "stdlib.h" -#include "time.h" - -// 插入排序 -void insert_sort(int *a,int length){ - - int tmp ; - int i,j; - for (i = 1; i < length; ++i) - { - - for ( tmp=a[i],j=i-1 ; j>=0 && a[j] > tmp ; j--) - { - a[j+1]=a[j]; - } - // j+1是插入的位置 - a[j+1]=tmp; - - } - -} - - -// 选择排序 - -void select_sort(int *a,int length){ - - int min_index,tmp; - int j; - - for (int i = 0; i < length; ++i) - { - for (j = i+1 ,min_index=i; j < length; ++j)// 不能写成for (int j = i+1 ,min_index=i; j < length; ++j) - { - if (a[min_index]>a[j]) - { - min_index=j; - } - } - - //min_index是最小的元素的index - if (min_index!=i) - { - tmp=a[i]; - a[i]=a[min_index]; - a[min_index]=tmp; - - } - - } - -} - - -// 冒泡排序 - -void bubble_sort(int *a, int length){ - - int tmp ; - - for (int i = 0; i < length-1; ++i) // 第i轮排序 - { - for (int j = 0; j < length-i; ++j) - { - if (a[j] > a[j+1]) - { - tmp = a[j]; - a[j] = a[j+1]; - a[j+1] = tmp; - } - } - - } - -} - - -// 快速排序 - -// 挖坑填数,2边向中间扫描 -int partion(int *a, int start,int end){ - - int i=start,j=end; - int tmp = a[i]; // 这里要做越界检查 - - while(i=tmp){ - j--; - } - if (i= a[max]) - { - break; - } - else - { - heap_swop(&a[tmp],&a[max]); - tmp = 2*tmp+1; - } - - } - -} - - -// 从第一个非叶子节点a[(length-2)/2],开始做调整, 跟自己的子节点比较,把最大的孩子换上来就是创建最大堆, -//反之,把最小的孩子换上来就是创建最小堆 一直到a[0] -void heap_build(int *a,int length){ - - - for (int i = (length-2)/2; i >=0 ; --i) - { - // 三个数里取最大的一个 a[i],a[2i+1],a[2i+2],跟a[i]交换;然后是 a[(i-1)/2],a[i],a[i+1] .. 一直到a[0] - heap_public_adjust(a,i,length); - - } - -} - -// 自顶向下调整 -void heap_adjust(int *a,int length){ - - heap_public_adjust(a,0,length); //对0号调整 -} - - -void heap_sort(int *a, int length){ - - // 建立堆 大根堆,递增排序 - heap_build(a,length); - - for (int i = length-1; i >0; --i) - { - //交换 - heap_swop(&a[0],&a[i]); - //调整 - heap_adjust(a,i); - } - -} - - - -// 归并排序 - -// 合并2个有序数组,分配一个临时空间,装a,b的结果,最后,将合并结果拷贝到数组A,是否临时空间 -void merge_array(int *a,int size_a,int *b, int size_b){ - - - int *tmp = malloc( (size_a+size_b)*sizeof(int) ); - int i,j,k; - i=j=k=0; - - while(ib[j])?b[j++]:a[i++]; - } - - while(i1) - { - - merge_sort(a,length/2); - merge_sort(a+length/2,length-length/2); - - merge_array(a,length/2,a+length/2,length-length/2); - } - - - -} - - -/////////////////////////////////////////////// - - -#define Max_Number 5000 - -int main(){ - - //int a[] = {4,87,2,32,5,2,9,49,49,23,45,2,41}; - //准备5000个数 - int a[Max_Number]; - for (int i = 0; i < Max_Number; ++i) - { - a[i]=rand()%Max_Number; - } - - clock_t start,finish; - - - - start = clock(); - merge_sort(a,sizeof(a)/sizeof(int)); // 0.002s,可以看到,归并排序还是很快的 - //heap_sort(a,sizeof(a)/sizeof(int)); // 有buggggggg - //quicksort(a,0,sizeof(a)/sizeof(int)-1); // 0.01s - //insert_sort(a,sizeof(a)/sizeof(int)); // 3.85s - //select_sort(a,sizeof(a)/sizeof(int)); // 5.3s - //bubble_sort(a,sizeof(a)/sizeof(int)); // 12.5s - finish = clock(); - - printf("after sort:\n"); - for (int i = 0; i < sizeof(a)/sizeof(int); ++i) - { - printf(" %d ",a[i]); - } - printf("time eclipse: %.6f sec\n", (double)(finish-start)/CLOCKS_PER_SEC); // CLOCKS_PER_SEC 1000 clock()是毫秒 - - return 0; -} - - diff --git a/images/bubble-sort.gif b/images/bubble-sort.gif deleted file mode 100644 index 03236ca..0000000 Binary files a/images/bubble-sort.gif and /dev/null differ diff --git a/images/fast-sort.gif b/images/fast-sort.gif deleted file mode 100644 index c107105..0000000 Binary files a/images/fast-sort.gif and /dev/null differ diff --git a/images/heap-sort.gif b/images/heap-sort.gif deleted file mode 100644 index f73ccf2..0000000 Binary files a/images/heap-sort.gif and /dev/null differ diff --git a/images/merge-sort.gif b/images/merge-sort.gif deleted file mode 100644 index 40f3c04..0000000 Binary files a/images/merge-sort.gif and /dev/null differ diff --git a/images/select-sort.gif b/images/select-sort.gif deleted file mode 100644 index 3261137..0000000 Binary files a/images/select-sort.gif and /dev/null differ diff --git a/images/shell-sort.gif b/images/shell-sort.gif deleted file mode 100644 index 8138ef7..0000000 Binary files a/images/shell-sort.gif and /dev/null differ