1.设计一个数据结构html
2.实现某个算法用到了某个/某几个数据结构java
能够认为是一个集合,而且提供集合上的若干操做node
-Queue -Stack -Hash
-Tree
O(1) Push Pop Topc++
O(1) Push Pop Top面试
算法要具象化,数据结构也要具象化算法
栈好像一个大箱子,往里面一本本放书,拿的时候得从最上面的拿。express
queue就是排队,从后面进,从前面出。数组
用哪一种底层的数据结构实现Queue呢?安全
循环数组和动态数组:数据结构
2.1 循环数组1 2 3 4…10十个坑, 1不用了, 把1 删了,而后加11进去, 11占得是1的坑, 每一个坑能够循环利用.
2.2 动态数组就是c++里的vector java 里的array list
开一百个坑, 用满了
而后开2*100个, 把前100个copy过去, 再把前100个删掉.
O(key_size) Insert / O(key_size) Find / O(key_size) Delete
好比key一个整数, 四个字节
实际的插入, 查找, 删除的时间复杂度是O(4)
使命: 对于任意的key,获得一个固定且无规律的介于0~capacity-1的整数
理解: hash map能够理解为一个大数组, hash function 就是找到这个数组的index, 而后把一对存进去
MD5 SHA-1 SHA2 太复杂, 加密用的,此外
最简单的是取模,好比key%31转换为31进制, 31为经验值
• 边乘边取模, 以防越界
• java和c++都会自动把越界的减掉
通常hash function是针对string即char的,由于其它的数据形式均可以转化成char
好比int是4byte的,就是4个char
double是8byte的,就是8个char
若是一个class是{2int加上1double}就能够等同一个8+8的string
貌似好像java下面是每一个字节×33+字节对的整数取模,其实也就是转换成33进制,再取模
hash function的设计要求是:越乱越好,越没有规律越好
可是若是有一列数101,201,301,401。。那就坑爹了。。。
其中须要注意的是close hash,在删除一个key以后,要标注可用,而不是空位,具体以下:
加入7,3,12三个数字到一个hash function为%5的table里,假设前面一小部分以下:
其中7加到index为2的,3加到index为3的,到12的时候,算出来的index是2,可是2已经被占了,因此向后挪一个,去看看3,结果3也被占了,因此12就被塞到了index为4的地方
当删除3的时候,不能直接把index为3的位置直接标空位,而应该标available,这样查询12的时候,会去2找,没有去看3,发现available,知道以前被占过,而后接着向后找
open hashing 和close hashing 都要rehashing
空间大 非空间
空间小 查找时间长
因此trade off一下
The size of the hash table is not determinate at the very beginning. If the total size of keys is too large (e.g. size >= capacity / 10), we should double the size of the hash table and rehash every keys.
public class Solution { /** * @param hashTable: A list of The first node of linked list * @return: A list of The first node of linked list which have twice size */ public ListNode[] rehashing(ListNode[] hashTable) { // write your code here if (hashTable.length <= 0) { return hashTable; } int newcapacity = 2 * hashTable.length; ListNode[] newTable = new ListNode[newcapacity]; for (int i = 0; i < hashTable.length; i++) { while (hashTable[i] != null) { int newindex = (hashTable[i].val % newcapacity + newcapacity) % newcapacity; if (newTable[newindex] == null) { newTable[newindex] = new ListNode(hashTable[i].val); // newTable[newindex].next = null; } else { ListNode dummy = newTable[newindex]; while (dummy.next != null) { dummy = dummy.next; } dummy.next = new ListNode(hashTable[i].val); } hashTable[i] = hashTable[i].next; } } return newTable; } }
size是实际被占的,若是实际被占的空间超过十分之一,冲突率过高。若是数组须要开的更大,就须要开一个更大的数组,而且把原来的小的copy过去,相似于动态数组,可是不少时候hash function就会变,因此最好不要轻易折腾,举个栗子:
原本有[4,1,2,3]四个数,其中他们的位置按照%4获得的
咱们要扩充数组到八个坑,咱们开八个坑,要把1,2,3,4挪过去。可是此次1,2,3,4要根据%8来找他们的位置,而不是直接copy过去,因此增长了很多计算量
由于哈希表只膨胀, 不收缩, 因此对因而不是加一个又删一个的操做, 就要偶尔destroy了再重建一个
cache的原理就是比较hot的条目放速度快的地方存着(内存),不hot的放速度慢的(硬盘),评价hot与否的原则是:
LRU: last recent used 时间戳, 坑不够, 淘汰最老的(此外还有LFU: last frequent used,不要求掌握)
假设一个LRU cache只有三个坑,最近的是
2->1->3
咱们如今出现一个新的使用是2 咱们要变成
1->3->2
出现了5,变成
3->2->5
LRU中,由于有冲突,因此须要链表, 而给一个key, 须要的知道链表在哪儿
因此实现方法为 linked list+ hashmap
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and set.
get(key) – Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
set(key, value) – Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.
这个其中set和get都是visit
在2日后挪的时候, hash不变, 可是1和3受影响, 因此可用doubly linked list
每一个key对应的value的值是prev的点
2挪到尾巴, 1.next = 1.next.next就好了
public class LRUCache { private class Node{ Node prev; Node next; int key; int value; public Node(int key, int value) { this.key = key; this.value = value; this.prev = null; this.next = null; } } private int capacity; private HashMap hs = new HashMap(); private Node head = new Node(-1, -1); private Node tail = new Node(-1, -1); public LRUCache(int capacity) { this.capacity = capacity; tail.prev = head; head.next = tail; } public int get(int key) { if( !hs.containsKey(key)) { return -1; } // remove current Node current = hs.get(key); current.prev.next = current.next; current.next.prev = current.prev; // move current to tail move_to_tail(current); return hs.get(key).value; } public void set(int key, int value) { if( get(key) != -1) { hs.get(key).value = value; return; } if (hs.size() == capacity) { hs.remove(head.next.key); head.next = head.next.next; head.next.prev = head; } Node insert = new Node(key, value); hs.put(key, insert); move_to_tail(insert); } private void move_to_tail(Node current) { current.prev = tail.prev; tail.prev = current; current.prev.next = current; current.next = tail; } }
O(log N) Add / O(log N) Remove / O(1) Min or Max
用于设计最大最小值的问题
priority queue是一个阉割版的heap, 叫作queue,实际上是heap(只实现了部分heap的功能), 每次优先级最高的出列
只能add一个和remove一个, 删除是O(n)
longest palindrome substring标准算法是叫manche algorithm O(n), 另有基于它的O(nlogn)算法, 可是面试写出O(n^2)就能够
Ugly number is a number that only have factors 2, 3 and 5.
Design an algorithm to find the nth ugly number. The first 10 ugly numbers are 1, 2, 3, 4, 5, 6, 8, 9, 10, 12…
// version 1: O(n) scan class Solution { /** * @param n an integer * @return the nth prime number as description. */ public int nthUglyNumber(int n) { List uglys = new ArrayList(); uglys.add(1); int p2 = 0, p3 = 0, p5 = 0; // p2, p3 & p5 share the same queue: uglys for (int i = 1; i < n; i++) { int lastNumber = uglys.get(i - 1); while (uglys.get(p2) * 2 <= lastNumber) p2++; while (uglys.get(p3) * 3 <= lastNumber) p3++; while (uglys.get(p5) * 5 <= lastNumber) p5++; uglys.add(Math.min( Math.min(uglys.get(p2) * 2, uglys.get(p3) * 3), uglys.get(p5) * 5 )); } return uglys.get(n - 1); } }; // version 2 O(nlogn) HashMap + Heap class Solution { /** * @param n an integer * @return the nth prime number as description. */ public int nthUglyNumber(int n) { // Write your code here Queue Q = new PriorityQueue(); HashSet inQ = new HashSet(); Long[] primes = new Long[3]; primes[0] = Long.valueOf(2); primes[1] = Long.valueOf(3); primes[2] = Long.valueOf(5); for (int i = 0; i < 3; i++) { Q.add(primes[i]); inQ.add(primes[i]); } Long number = Long.valueOf(1); for (int i = 1; i < n; i++) { number = Q.poll(); for (int j = 0; j < 3; j++) { if (!inQ.contains(primes[j] * number)) { Q.add(number * primes[j]); inQ.add(number * primes[j]); } } } return number.intValue(); } };
Implement a data structure, provide two interfaces:
add(number). Add a new number in the data structure.
topk(). Return the top k largest numbers in this data structure. k is given when we create the data structure.
top之间比最弱, 用min heap.
add O(logk)
topk O(klogk)
可是kth largest number 用quick select O(n)
heap是Nlogk, 时长要知道前k个是谁, 是流动的活数据
而quick sort是O(N), 找从小到大第k个, 是离线的死数据, 一次行的
public class Solution { private int maxSize; private Queue minheap; public Solution(int k) { minheap = new PriorityQueue(); maxSize = k; } public void add(int num) { if (minheap.size() < maxSize) { minheap.offer(num); return; } if (num > minheap.peek()) { minheap.poll(); minheap.offer(num); } } public List topk() { Iterator it = minheap.iterator(); List result = new ArrayList(); while (it.hasNext()) { result.add((Integer) it.next()); } Collections.sort(result, Collections.reverseOrder()); return result; } };
Merge k sorted linked lists and return it as one sorted list.
我只有1G内存,可是要排序4G的数组
就分4个1G的分别排好, 再合并
经典实现用heap,时间O(Nlogk), 谁小谁出列, k个数找最小, 用heap
重点是priority queue的comparator的实现
从小到大是第一个参数a减第二个参数b
从大到小是第二个参数减第一个参数
第一个参数减第二个参数为何是从小到大呢?首先咱们看定义
Syntax:
In their implementation in the C++ Standard Template Library, priority queues take three template parameters:1
2 template < class T, class Container = vector<T>,
class Compare = less<typename Container::value_type> > class priority_queue;
Where the template parameters have the following meanings:
T: Type of the elements.
Container: Type of the underlying container object used to store and access the elements.
Compare: Comparison class: A class such that the expression comp(a,b), where comp is an object of this class and a and b are elements of the container, returns true if a is to be placed earlier than b in a strict weak ordering operation. This can either be a class implementing a function call operator or a pointer to a function. This defaults to less<T>, which returns the same as applying the less-than operator (a<b).
The priority_queue object uses this expression when an element is inserted or removed from it (using push or pop, respectively) to grant that the element popped is always the greater in the priority queue.
参考定义呢,comparator为真的时候,就是a-b>0, a优先级高,先出列,a而后b,这不是由大到小么?反了啊。。。此处若有知道为何,请指教
答案找到了,能够参考:http://www.cnblogs.com/cielosun/p/5654595.html,如下为转载:
首先函数在头文件<queue>中,归属于命名空间std,使用的时候须要注意。
队列有两种经常使用的声明方式:
std::priority_queue<T> pq; std::priority_queue<T, std::vector<T>, cmp> pq;
第一种实现方式较为经常使用,接下来我给出STL中的对应声明,再加以解释。
template<class _Ty, class _Container = vector<_Ty>, class _Pr = less<typename _Container::value_type> > class priority_queue
你们能够看到,默认模板有三个参数,第一个是优先队列处理的类,第二个参数比较有特色,是容纳优先队列的容器。实际上,优先队列是由这个容器+C语言中关于heap的相关操做实现的。这个容器默认是vector,也能够是dequeue,由于后者功能更强大,而性能相对于vector较差,考虑到包装在优先队列后,后者功能并不能很好发挥,因此通常选择vector来作这个容器。第三个参数比较重要,支持一个比较结构,默认是less,默认状况下,会选择第一个参数决定的类的<运算符来作这个比较函数。
接下来开始坑爹了,虽然用的是less结构,然而,队列的出队顺序倒是greater的先出!就是说,这里这个参数其实很傲娇,表示的意思是若是!cmp,则先出列,无论这样实现的目的是啥,你们只能接受这个实现。实际上,这里的第三个参数能够更换成greater,像下面这样:
std::priority_queue<T, std::vector<T>, greater<T>> pq;
通常你们若是是自定义类就干脆重载<号时注意下方向了,没人在这里麻烦,这个选择基本上是在使用int类还想小值先出列时。
从上面的剖析咱们也就知道了,想要让自定义类可以使用优先队列,咱们要重载小于号。
class Student { int id; char name[20]; bool gender; bool operator < (Student &a) const { return id > a.id; } };
就拿这个例子说,咱们想让id小的先出列,怎么办,就要很违和的给这个小于符号重载成其实是大于的定义。
若是咱们不使用自定义类,又要用非默认方法去排序怎么办?就好比说在Dijkstra中,咱们固然不会用点的序号去排列,不管是正序仍是反序,咱们想用点到起点的距离这个值来进行排序,咱们怎样作呢?细心的读者在阅读个人有关Dijkstra那篇文章时应该就发现了作法——自定义比较结构。优先队列默认使用的是小于结构,而上文的作法是为咱们的自定义类去定义新的小于结构来符合优先队列,咱们固然也能够自定义比较结构。自定义方法以及使用以下,我直接用Dijkstra那篇的代码来讲明:
int cost[MAX_V][MAX_V];
int d[MAX_V], V, s;
//自定义优先队列less比较函数
struct cmp
{
bool operator()(int &a, int &b) const
{
//由于优先出列断定为!cmp,因此反向定义实现最小值优先
return d[a] > d[b];
}
};
void Dijkstra()
{
std::priority_queue<int, std::vector<int>, cmp> pq;
pq.push(s);
d[s] = 0;
while (!pq.empty()) { int tmp = pq.top();pq.pop(); for (int i = 0;i < V;++i) { if (d[i] > d[tmp] + cost[tmp][i]) { d[i] = d[tmp] + cost[tmp][i]; pq.push(i); } } } }
http://www.cnblogs.com/cielosun/p/5654595.html转载结束。
同时推荐http://www.cnblogs.com/cielosun/p/6958802.html,是stack,queue和priority_queue的c++操做集合
public class Solution { private Comparator ListNodeComparator = new Comparator() { public int compare(ListNode left, ListNode right) { return left.val - right.val; } }; public ListNode mergeKLists(List lists) { if (lists == null || lists.size() == 0) { return null; } Queue heap = new PriorityQueue(lists.size(), ListNodeComparator); for (int i = 0; i < lists.size(); i++) { if (lists.get(i) != null) { heap.add(lists.get(i)); } } ListNode dummy = new ListNode(0); ListNode tail = dummy; while (!heap.isEmpty()) { ListNode head = heap.poll(); tail.next = head; tail = head; if (head.next != null) { heap.add(head.next); } } return dummy.next; } }
k个的时候劈一半
一半1~k/2 一半2/k+1~k
每层用时间O(N), 一共logk层
public class Solution { /** * @param lists: a list of ListNode * @return: The head of one sorted list. */ public ListNode mergeKLists(List lists) { if (lists.size() == 0) { return null; } return mergeHelper(lists, 0, lists.size() - 1); } private ListNode mergeHelper(List lists, int start, int end) { if (start == end) { return lists.get(start); } int mid = start + (end - start) / 2; ListNode left = mergeHelper(lists, start, mid); ListNode right = mergeHelper(lists, mid + 1, end); return mergeTwoLists(left, right); } private ListNode mergeTwoLists(ListNode list1, ListNode list2) { ListNode dummy = new ListNode(0); ListNode tail = dummy; while (list1 != null && list2 != null) { if (list1.val < list2.val) { tail.next = list1; tail = list1; list1 = list1.next; } else { tail.next = list2; tail = list2; list2 = list2.next; } } if (list1 != null) { tail.next = list1; } else { tail.next = list2; } return dummy.next; } }
本质和上面一个差很少
public class Solution { /** * @param lists: a list of ListNode * @return: The head of one sorted list. */ public ListNode mergeKLists(List lists) { if (lists == null || lists.size() == 0) { return null; } while (lists.size() > 1) { List new_lists = new ArrayList(); for (int i = 0; i + 1 < lists.size(); i += 2) { ListNode merged_list = merge(lists.get(i), lists.get(i+1)); new_lists.add(merged_list); } if (lists.size() % 2 == 1) { new_lists.add(lists.get(lists.size() - 1)); } lists = new_lists; } return lists.get(0); } private ListNode merge(ListNode a, ListNode b) { ListNode dummy = new ListNode(0); ListNode tail = dummy; while (a != null && b != null) { if (a.val < b.val) { tail.next = a; a = a.next; } else { tail.next = b; b = b.next; } tail = tail.next; } if (a != null) { tail.next = a; } else { tail.next = b; } return dummy.next; } }
1.结构特性:假设一个二叉树的深度为n。为了知足彻底二叉树的要求,该二叉树的前n-1层必须填满,第n层也必须按照从左到右的顺序被填满。即二叉树严格遵循从上到下,再从左到右的方式构造
2.值特性:最大或最小的关系
若是是min heap,则父亲要小于全部的儿子。
max heap, 是父亲要大于全部的儿子。
注意各个儿子之间没有大小关系, 左儿子可能比右儿子大, 也可能小.
如下插入和删除的操做来源于其它博客,侵删。
——————————————————————
在插入操做的时候,会破坏上述堆的性质,因此须要进行名为sift up的操做,以进行恢复。
咱们插入节点2:
删除操做只能删除根节点。
sift down: 将节点不断的和子节点比较。若是节点比两个子节点中小的那一个大,则和该子节点交换。直到last节点不大于任一子节点,或者last节点成为叶节点。
删除根节点1。如图:
——————————————————————
当咱们插入或者删除结点的时候, 就是一路换大或者换小,最多换logN次,因此插入或者删除操做的时间复杂度都是O(logn)
priority queue因为是阉割版的缘由,删除任意节点的时间是O(n), 它只能for一遍, 而后删
heap的任意节点的删除操做是O(logn)时间:
此外:
Array 0 1 2 3 4 5 6 7 8 9 5 1 2 3 4 5
对于一个下标位k的节点:
又叫red black tree / balanced binary tree, 全部操做logn
最小一路往左, logn
最大一路往右, logn
适合用来解决data stream median 的问题:
Data Stream Median
Numbers keep coming, return the median of numbers at every time a new number added.
Clarification
What’s the definition of Median?
Example
For numbers coming list: [1, 2, 3, 4, 5], return [1, 1, 2, 2, 3].
For numbers coming list: [4, 5, 1, 3, 2, 6, 0], return [4, 4, 4, 3, 3, 3, 3].
For numbers coming list: [2, 20, 100], return [2, 2, 20].
用两个堆, max heap 和 min heap. 维持两个堆的大小相等(max堆能够比min堆多一个). 则max堆的顶即为median值.
Implement a stack with min() function, which will return the smallest number in the stack.
It should support push, pop and min operation all in O(1) cost.
Notice
min operation will never be called if there is no number in the stack.
Example
Tags
Related Problems
push(1)
pop() // return 1
push(2)
push(3)
min() // return 2
push(1)
min() // return 1
最暴力的解法:
min()的时候for一遍剩下的元素,找到最小的
别担忧,勇敢的说,谁的第一反应都是这个。
稍微高级点的解法:
push的时候记录最小值,push+min
-要是加上pop呢?
两个stack,加一个最小值的stack,pop原来的数的时候,也从最小值stack里pop出来一个相应的
As the title described, you should only use two stacks to implement a queue’s actions.
The queue should support push(element), pop() and top() where pop is pop the first(a.k.a front) element in the queue.
Both pop and top methods should return the value of first element.
Example
push(1)
pop() // return 1
push(2)
push(3)
top() // return 2
pop() // return 2
准备两个stack,stack1和stack2
放stack1里放正了,再倒到stack2里 就倒过来了,push就push倒stack1里,pop要从stack2里pop
Given n non-negative integers representing the histogram’s bar height where the width of each bar is 1, find the area of largest rectangle in the histogram.
Above is a histogram where width of each bar is 1, given height = [2,1,5,6,2,3].
The largest rectangle is shown in the shaded area, which has area = 10 unit.
Example
Tags
Related Problems
Given height = [2,1,5,6,2,3],
return 10.
直接的是O(n^{2}) 二重循环(枚举,搜索)
若是想提速的话,能够有两种O(nlogn)和O(n)两个可能
nlogn:
排序 nlogn
heap logn
二分要排序,可是这道题一排序就乱了
核心点:最矮的那根木头
其实就是for每根木头i,向左摆找第一个比他矮的x,再向右找第一个比他矮的y,
算出height[i]*(y-x-1)
此时,引出了一个坑爹的数据形式:
单调栈是个找左边第一个的比它小,右边第一个比它小的。
给出一组数据 2 1 5 6 2 3
流程以下:
先把2塞进去,ok, 比1大
把1塞进去,1比2小,把2踢出来,左边没有,右边第一个比它小的是1
把5塞进去,ok, 比1大
把6塞进去,ok,比1和5 都大
把2塞进去,不行了,比5和6小,从和2接近的踢
即把6踢出去,右边第一个比它小的是2,左边第一个比它小的是5
再把5踢出去,右边第一个比它小的是2,左边第一个比它小的是1
把3塞进去再,ok比1,2大
3是最后一个数,以后再塞一个非正整数的-1
即3被踢出来,右边第一个比它小的是-1,左边是2
接着2被踢出来,右边第一个比它小的是-1,左边是1
接着1被踢出来左边无,右边-1
为了更好的理解单调栈,再跑一组数据,这组咱们记录左边和右边第一个比它小的数的index:
4,3,2,1,5,6,2
代码以下:
遇到for()里面又套了一个while的这种要当心,乍一看容易以为时间复杂度是O(n^{2}), 可是平均复杂度颇有可能只是O(n).
由于要分析最坏的状况是否是能每次都发生,是否是为了攒一次最坏的状况,要耗费以前不少次以前的。
这种要蓄一波才能发射的,要算平均。
单调栈适合把O(n^{2})变为O(n)。
Given a 2D boolean matrix filled with False and True, find the largest rectangle containing all True and return its area.
Given a matrix:
[
[1, 1, 0, 0, 1],
[0, 1, 0, 0, 1],
[0, 0, 1, 1, 1],
[0, 0, 1, 1, 1],
[0, 0, 0, 0, 1]
]
return 6.
对每一行作底,解前面的矩形问题。
string的hash table 时间复杂度是O(L),其中L是string的长度
O(key的长度)是hash function的时间复杂度
BFS的实现原理是hash map+ queque, 用到了两个最经常使用的数据结构,因此很是常考。