1. 程式人生 > >九章演算法筆記 8.雜湊表與堆 Hash & Heap

九章演算法筆記 8.雜湊表與堆 Hash & Heap

大綱 cs3k.com

  1. 資料結構概述
  2. 雜湊表 Hash: a.原理  b.應用
  3. 堆 Heap: a.原理    b.應用-優先佇列 Priority Queue  c.替代品-TreeMap

 

資料結構的兩類問題

cs3k.com

1.設計一個數據結構

2.實現某個演算法用到了某個/某幾個資料結構

什麼是資料結構

可以認為是一個集合,並且提供集合上的若干操作

LINEAR DATA STRUCTURE,通常用陣列實現

 -Queue
 -Stack
 -Hash

TREE DATA STRUCTURE,通常用指標

 -Tree

QUEUE : BFS

O(1) Push Pop Top

STACK : DFS

O(1) Push Pop Top

題外話

演算法要具象化,資料結構也要具象化

棧好像一個大箱子,往裡面一本本放書,拿的時候得從最上面的拿。

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, 用到了兩個最常用的資料結構,所以非常常考。