九章算法笔记 8.哈希表与堆 Hash & Heap

大纲 cs3k.com

  1. 数据结构概述
  2. 哈希表 Hash: a.原理  b.应用
  3. 堆 Heap: a.原理    b.应用-优先队列 Priority Queue  c.替代品-TreeMap

 

数据结构的两类问题

cs3k.com

1.设计一个数据结构html

2.实现某个算法用到了某个/某几个数据结构java

什么是数据结构

能够认为是一个集合,而且提供集合上的若干操做node

LINEAR DATA STRUCTURE,一般用数组实现

-Queue
 -Stack
 -Hash

TREE DATA STRUCTURE,一般用指针

-Tree

QUEUE : BFS

O(1) Push Pop Topc++

STACK : DFS

O(1) Push Pop Top面试

题外话

算法要具象化,数据结构也要具象化算法

栈好像一个大箱子,往里面一本本放书,拿的时候得从最上面的拿。express

queue就是排队,从后面进,从前面出。数组

Queue的实现

用哪一种底层的数据结构实现Queue呢?安全

  1. 用linked list的实现很是直观

     

  2. 循环数组和动态数组:数据结构

2.1 循环数组1 2 3 4…10十个坑, 1不用了, 把1 删了,而后加11进去, 11占得是1的坑, 每一个坑能够循环利用.

2.2 动态数组就是c++里的vector java 里的array list

开一百个坑, 用满了

而后开2*100个, 把前100个copy过去, 再把前100个删掉.

Hash

cs3k.com

时间复杂度

O(key_size) Insert / O(key_size) Find / O(key_size) Delete

好比key一个整数, 四个字节

实际的插入, 查找, 删除的时间复杂度是O(4)

hash table VS hash map VS hash set

  1. hash set只有key 没有value, 去重的时候用
  2. hash table支持线程安全,能够多个线程同事调用一个hash table, 不会出问题
  3. hash map不支持线程安全, 多个线程一块儿搞一个hash map会搞砸ps: 由于加锁和解锁很慢, 因此hash table会性能低一些

hash function/ hash code

使命: 对于任意的key,获得一个固定且无规律的介于0~capacity-1的整数

理解: hash map能够理解为一个大数组, hash function 就是找到这个数组的index, 而后把一对存进去

著名的hash 算法

cs3k.com

MD5 SHA-1 SHA2 太复杂, 加密用的,此外

  1. char->255 整数

     

  2. 最简单的是取模,好比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。。那就坑爹了。。。

hash function的collision

cs3k.com

  1. open hash table:有collision就存个linked list,拉链法相似于上厕所的时候, 看上了一个坑, 就等它, 就在后面排着若是要查找的时候, 就从排着的地方for一遍, 看有没有
  2. close hash table:有collision就占下一个坑,占坑法相似于上厕所占坑, 坑占了就找下一个, 你占我, 我就占别人的

其中须要注意的是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,知道以前被占过,而后接着向后找

rehashing

open hashing 和close hashing 都要rehashing

空间大VS空间小

空间大 非空间

空间小 查找时间长

因此trade off一下

Rehashing

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; } }

标准:占了超过10%就要rehashing

size是实际被占的,若是实际被占的空间超过十分之一,冲突率过高。若是数组须要开的更大,就须要开一个更大的数组,而且把原来的小的copy过去,相似于动态数组,可是不少时候hash function就会变,因此最好不要轻易折腾,举个栗子:

原本有[4,1,2,3]四个数,其中他们的位置按照%4获得的

咱们要扩充数组到八个坑,咱们开八个坑,要把1,2,3,4挪过去。可是此次1,2,3,4要根据%8来找他们的位置,而不是直接copy过去,因此增长了很多计算量

哈希重建

由于哈希表只膨胀, 不收缩, 因此对因而不是加一个又删一个的操做, 就要偶尔destroy了再重建一个

LRU cache 和LFU cache

cs3k.com

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

LRU Cache

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

这里写图片描述

  1. 用doubly linked list实现:

在2日后挪的时候, hash不变, 可是1和3受影响, 因此可用doubly linked list

  1. 用singly 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; } }

heap

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

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(); } };

Top k Largest Numbers II

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)

何时用QUICK SELECT 何时用HEAP呢

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 Lists

cs3k.com

Merge k sorted linked lists and return it as one sorted list.

相似的题有external sorting

我只有1G内存,可是要排序4G的数组

就分4个1G的分别排好, 再合并

1. k路归并算法, 用heap

经典实现用heap,时间O(Nlogk), 谁小谁出列, k个数找最小, 用heap

2. 用priority queue 的实现

重点是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; } } 

3.分治法

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; } }

4.两两归并

本质和上面一个差很少

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; } }

heap是个最优二叉树, 有如下两个特性

1.结构特性:假设一个二叉树的深度为n。为了知足彻底二叉树的要求,该二叉树的前n-1层必须填满,第n层也必须按照从左到右的顺序被填满。即二叉树严格遵循从上到下,再从左到右的方式构造

2.值特性:最大或最小的关系

若是是min heap,则父亲要小于全部的儿子。

max heap, 是父亲要大于全部的儿子。

注意各个儿子之间没有大小关系, 左儿子可能比右儿子大, 也可能小.

如下插入和删除的操做来源于其它博客,侵删。

——————————————————————

插入 add – O(logn)时间:

cs3k.com

在插入操做的时候,会破坏上述堆的性质,因此须要进行名为sift up的操做,以进行恢复。

  1. 加入一个点放第一个能放的位置, 即最下一层最左的空位。
  2. 不断和父节点进行比较,直到比父节点小。sift up 操做 :若是new节点比父节点小,那么交换二者。交换以后,继续和新的父节点比较…… 直到new节点不比父节点小,或者new节点成为根节点。

咱们插入节点2:

这里写图片描述

pop() – O(logn)时间:

  1. 根节点和最下面一层最右的节点换, 而后删了如今的最右下的
  2.  新的根节点不断和本身的儿子最小的那个比较,而后和儿子中最小的那个换。 直到last节点不大于任一子节点, 或者last节点成为叶节点。

删除操做只能删除根节点。

sift down: 将节点不断的和子节点比较。若是节点比两个子节点中小的那一个大,则和该子节点交换。直到last节点不大于任一子节点,或者last节点成为叶节点。

删除根节点1。如图:

这里写图片描述

——————————————————————

当咱们插入或者删除结点的时候, 就是一路换大或者换小,最多换logN次,因此插入或者删除操做的时间复杂度都是O(logn)

delete() / remove()任意节点 – O(logn)时间:

priority queue因为是阉割版的缘由,删除任意节点的时间是O(n), 它只能for一遍, 而后删

heap的任意节点的删除操做是O(logn)时间:

  1. 用hash map找到这个点, 并把这个点和最下一层的最右互换
  2. 删了右下的点,而后换过去的和父节点进行比较 >= 父节点, sift up  ; < 父节点, sift down

此外:

  1. hasp map查找一个节点的位置, 构造hash map <key节点的值, value节点在堆里的位置. 因此能够要求堆必须不能有重复数字
  2. 堆得节点数固定,则堆得形状固定, 用数组就能够储存这个堆:数组第0位存堆的大小,好比一个大小为10的存5个节点的堆:

img_1550

    Array    0   1   2   3   4   5   6   7   8   9

             5   1  2   3   4   5

对于一个下标位k的节点:

  1. 父节点下标 k/2
  2. 左儿子下标 2k
  3. 右儿子下标 2k+1

Tree Map

又叫red black tree / balanced binary tree, 全部操做logn

最小一路往左, logn

最大一路往右, logn

priority queque

适合用来解决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?

  • Median is the number that in the middle of a sorted array. If there are n numbers in a sorted array A, the median is A[(n – 1) / 2]. For example, if A=[1,2,3], median is 2. If A=[1,19], median is 1.

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值.

Min Stack

cs3k.com

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出来一个相应的

Implement Queue by Two Stacks

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

Largest Rectangle in Histogram

cs3k.com

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.

enter image description here

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:

  • 二分 for(1….到n, 每次logn)

     

  • 排序 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)。

Maximal Rectangle

cs3k.com

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.

对每一行作底,解前面的矩形问题。

hash table 要懂原理,会实现

cs3k.com

string的hash table 时间复杂度是O(L),其中L是string的长度

O(key的长度)是hash function的时间复杂度

BFS的实现原理是hash map+ queque, 用到了两个最经常使用的数据结构,因此很是常考。

相关文章
相关标签/搜索