九章算法高級班筆記3.數據結構(下)

  1. Heap c
  2. cs3k.com

  • Heap基本原理
  • Heap 問題的拓展
  • Hashheap
  • Hashheap 運用
  1. Stack
  • 反轉棧裏面元素
  • 單調棧的運用

 

Trapping Rain Water  cs3k.com

Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining.
enter image description herejava

灌水問題,要肯定基調柱子,就是水桶的桶邊。
對於每一個位置來講,重要的是:
它左邊的全部柱子的最大值a
它右邊的全部柱子的最大值b
a和b中比較小的那個和它本身的差值是這個柱子能灌的水量!
奶奶的,我開始繞了很久沒反應過來,快要懷疑本身的智商了。。。老師還說這是一道很簡單的題。。。阿西巴。。。
這裏取網上http://blog.csdn.net/wzy_1988/article/details/17752809 的理解:
首先,碰到這樣的題目不要慌張,挨個分析每一個A[i]能trapped water的容量,而後將全部的A[i]的trapped water容量相加便可
node

其次,對於每一個A[i]能trapped water的容量,取決於A[i]左右兩邊的高度(可延展)較小值與A[i]的差值,即volume[i] = [min(left[i], right[i]) – A[i]] * 1,這裏的1是寬度,若是the width of each bar is 2,那就要乘以2了。python

public class Solution {  
    public int trap(int[] A) {  
        // special case  
        if (A == null || A.length == 0) {  
            return 0;  
        }  
          
        int i, max, volume, left[] = new int[A.length], right[] = new int[A.length];  
  
        // from left to right  
        for (left[0] = A[0], i = 1, max = A[0]; i < A.length; i++) {  
            if (A[i] = 0; i--) {  
            if (A[i] < max) {  
                right[i] = max;  
            } else {  
                right[i] = A[i];  
                max = A[i];  
            }  
        }  
  
        // trapped water  
        for (volume = 0, i = 1; i  0) {  
                volume += tmp;  
            }  
        }  
  
        return volume;  
    }  
}

九章的算法更好一些,可是我容易繞暈, 原本想會上一種方法就行了,可是發現follow up須要一脈相承的算法,而後老子只好畫個圖理一理了,唉。。。
首先,對於初始數據[3, 0, 1, 4, 0, 1, 2],能夠畫以下的圖,縱軸是高度,橫軸是bar的位置。
這道題九章答案的算法我最後的理解是這樣的:
從最左到最右這個最大區間開始, 此區間的最左和最右邊的柱子記爲牆頭。
對於這個區間來講,左右哪一個牆頭矮我就站上去,而後往我站的位置向內最近的柱子澆水並記錄下澆了多少水。而後把剛纔澆水位置上設立新的牆頭, 牆頭的高度是包括它本身在內的所走過的最高值(牆具備向內延展性,對於裏面的柱子本身來講,牆在外面多遠無所謂)。再看新的兩個牆頭誰比較矮, 重複以上操做,知道高矮牆頭碰頭。
enter image description here
對於初始數據,咱們記最左index0和最右index6兩個爲牆頭。其中index0牆頭的高度是3,index6牆頭的高度是2. 因此index6位置的牆頭是矮牆頭,因此咱們站上去,向index5的位置灌水灌到矮牆頭index6的到高度。每一個步驟當時的牆頭用鉛筆的陰影表示。
enter image description here
index5的位置高度爲1,,最多能灌到高度爲2的矮牆頭index6的高度,就是灌了2-1 = 1的水,記錄下來如今的水量S= 1。水用波紋的紋理表示。
enter image description here
index5灌完水以後,就是把[index5,最右]區間內最高的牆挪到index5,而後新的牆頭就是index0的高度爲3的左牆頭,和index5高度爲2的右牆頭,明顯index5這個右牆頭比較矮:
enter image description here
而後咱們站在index5這個比較矮的牆頭,向index4灌水,灌了2-0=2的水,記錄下來。S以前等於1,又加了2,如今水量S爲1+2 = 3.
enter image description herec++

一個牆頭在被灌完水以後, 就要把它更新爲已路過的最高牆頭,因此如今index4被更新爲高度爲2的矮牆頭。從index4這個牆頭像內灌水,咱們發現灌不了,由於index3比它右邊全部的柱子都高,因此這部咱們水量加了0,也就是沒灌水,S不變仍然是3。
enter image description here
index3位置判斷灌水完畢以後,index3要更新爲它和它右邊最高的柱子的高度。index3右邊的柱子最高是index6的高度2, index3本身的高度是4。因此[index3 ~index6]最高的是3,而後咱們把index3更新爲新的高度爲4的右牆頭。如今出現了一個新狀況,左牆頭高度是3,右牆頭高度是4,比較矮的牆頭是index0的左牆頭:
enter image description here
咱們站在index0的位置接着向內灌水,灌了3-0=3的水,總水量S要加3,上一步S=3,如今S= 3+3 = 6:
enter image description here
index1的位置灌完水以後,咱們要在index1的位置上建牆頭。在index1的位置建完高度爲3的牆頭後,向index2位置灌水。灌了3-1=2的水,S= 6+2 =8;
enter image description here
而後在index=2的地方建牆頭,左牆頭在index2,是矮牆頭。向右邊index3灌水,灌不了,S不變。
enter image description here
index3灌水失敗後,把左牆頭更新爲index3.發現左右牆頭都在index3.結束啦
enter image description heregit

public class Solution {
    /**
     * @param heights: an array of integers
     * @return: a integer
     */
    public int trapRainWater(int[] heights) {
        // write your code here
        int left = 0, right = heights.length - 1; 
        int res = 0;
        if(left >= right)
            return res;
        int leftheight = heights[left];
        int rightheight = heights[right];
        while(left < right) {
            if(leftheight  heights[left]) {
                    res += (leftheight - heights[left]);
                } else {
                    leftheight = heights[left];
                }
            } else {
                right --;
                if(rightheight > heights[right]) {
                    res += (rightheight - heights[right]);
                } else {
                    rightheight = heights[right];
                }
            }
        }
        return res;
    }
}

Trapping Rain Water ii

cs3k.com

Given n x m non-negative integers representing an elevation map 2d where the area of each cell is 1 x 1, compute how much water it is able to trap after raining.
相似於上一道題的思路,只是牆頭是四周一圈,用heap存周圍一圈的牆,從最矮的牆頭開始灌水,而後把被灌的位置設置成牆:
如今四個邊的牆頭都存到最小堆heap裏面,而且記他們都visited,heap裏面有
8,12,13,12,13,12,13,12,12,13,13,13
最矮的牆頭是8,站上面
enter image description here
站在最矮的牆頭8上面,給8的上下左右灌水,發現左右都visited,上面沒有,下面是13沒有visited過比8高,沒法灌水。因此把8 pop出來,而後把第二行,第三列的13記爲新的牆頭,加入到heap裏面,並記爲visited, 此時的heap爲:
8,12,13,12, 13, 13,12,13,12,12,13,13,13
劃線表明pop, 斜體加粗表明新加入的元素。算法

enter image description here

咱們如今的牆頭裏面,最小的是12, 好多個12,咱們按照從左到右從上到下的順序看。
第一個12是第一行第一列的12,遍歷它的上下左右,發現都遍歷過,沒有新的元素能夠加到heap裏面。而後把它pop出來,heap爲:
8,12,13,12, 13,13,12,13,12,12,13,13,13
enter image description hereexpress

如今的牆頭裏面最矮的是第二個12,在第一行的第四列,把它pop出來。遍歷它的上下左右,發現都遍歷過,沒有新的元素能夠加到heap裏面。heap爲:數組

8,12,13, 1213,13,12,13,12,12,13,13,13數據結構

enter image description here

如今heap裏面最小的是第三個12,在第二行第四列,把它pop出來。咱們接着看它的上下左右,全都visited了,沒有新的元素能夠加到heap裏面,如今heap以下:app

8,12,13, 1213,13,12,13,12,12,13,13,13

enter image description here

而後看heap,heap裏面最小的是第三行第四列的12,是第四個12,把它pop出來。heap變成:
8,12,13,1213,13, 12,13, 12,12,13,13,13
它的上下都visited過,右邊沒有,左邊沒有visited過, 嘗試向左邊灌水:灌了12-10=2的水,如今的水量S=2。
enter image description here
10的位置灌完水以後,把它的位置記爲visited,再把它改形成它旁邊高度爲12的城牆,push到heap裏面:
8,12,13,1213,13, 12,13, 12,12,13,13,13,12

enter image description here

如今heap裏面最小的是剛剛改形成牆頭的第三行第三列,把它pop出來。它的上下左右沒有visited過得只有左。向左灌12-8=4的水。總水量S= 2+4=6:

8,12,13,1213,13, 12,13, 12,12,13,13,13,~12~

enter image description here

把第三行第二列的8建成高度爲12的牆頭,加入到heap裏面:

8,12,13,1213,13, 12,13, 12,12,13,13,13,~12~, 12

enter image description here

如今最小的是第三行第二列的12, pop出來,而後向上下左右灌水,灌水量爲12-4 = 8。總水量是S = 6+8 = 14。而後把4位置建成高爲12的牆頭,塞heap裏:

8,12,13,1213,13, 12,13, 12,12,13,13,13,~12~, 12

enter image description here

以後heap就把元素一個一個崩出來,以後在沒有能灌水的啦
結果就是14。

class Cell{
  public int x,y, h;
  Cell(){}
  Cell(int xx,int yy, int hh){
    x = xx;
    y = yy;
    h = hh;
  }
}
class CellComparator implements Comparator {
  @Override
  public int compare(Cell x, Cell y)
  {
    if(x.h > y.h)
      return 1;
    else if(x.h == y.h){
     return 0;
    }
    else {
      return -1;
    }
  }
} 


public class Solution {
  int []dx = {1,-1,0,0};
  int []dy = {0,0,1,-1};
  public  int trapRainWater(int[][] heights) {
       // write your code here
      if(heights.length == 0)  
        return 0;
      PriorityQueue q =  new PriorityQueue(new CellComparator());
      int n = heights.length;
      int m = heights[0].length;
      int [][]visit = new int[n][m];
      
      for(int i = 0; i < n; i++) {
        q.offer(new Cell(i,0,heights[i][0]));

        q.offer(new Cell(i,m-1,heights[i][m-1]));
        visit[i][0] = 1;
        visit[i][m-1] = 1;
      }
      for(int i = 0; i < m; i++) {
        q.offer(new Cell(0,i,heights[0][i]));

        q.offer(new Cell(n-1,i,heights[n-1][i]));
        visit[0][i] = 1;
        visit[n-1][i] = 1;

      }
      int ans = 0 ;
      while(!q.isEmpty()) {
        
        Cell now = q.poll();
        
        for(int i = 0; i < 4; i++) {
          
          int nx = now.x + dx[i];
          int ny = now.y + dy[i];
          if(0<=nx && nx < n && 0 <= ny && ny < m && visit[nx][ny] == 0) {
            visit[nx][ny] = 1;
            q.offer(new Cell(nx,ny,Math.max(now.h,heights[nx][ny])));
            ans = ans + Math.max(0,now.h - heights[nx][ny]);
          }
          
        }
      }
      return ans;
    } 
}

總結起來這道題用最小堆維護一個外部的牆頭的集合,而後:

  1. 建一個heap存牆頭,一個數組flag存有沒有visited
  2. 把第一行,第一列,最後一行,最後一列做爲最外圍的牆頭加入到heap裏面
  3. 蹦一個heap元素出來,看它上下左右(灌水高度和能不能加入到heap裏)

這道題的思考:

  1. 怎麼樣經過trapping rain water 1 拓展到這題的思路?都是站最矮的牆頭倒水.
  2. 怎麼樣想到利用堆?一堆流動數求最大或者最小值
  3. 怎麼想到由外向內遍歷??我沒想到啊…這個我忘了…
  4. 這道題爲什馬不用動歸?動歸用來解決重複的子問題,這道題木有重複子問題
  5. 這道題的時間複雜度?
    O  (  n*m      +         m*n  * log(2n+2m))
       加入heap              遍歷   堆操做

Data Stream Median

Numbers keep coming, return the median of numbers at every time a new number added.

首先想到加一個排一次序,那樣的話 時間複雜度是nnlogn
而後想到維持一個有序數組,加入的話,二分查找插入的位置, n個數, 二分查找用時logn, 查找後插入須要挪動,挪動每次是O(n)的操做, 總共n
(n+logn),即O(n^2)

最後解法是用堆來作, 維護兩個堆:

public class Solution {
    /**
     * @param nums: A list of integers.
     * @return: the median of numbers
     */
    private PriorityQueue maxHeap, minHeap;
    private int numOfElements = 0;

    public int[] medianII(int[] nums) {
        // write your code here
        Comparator revCmp = new Comparator() {
            @Override
            public int compare(Integer left, Integer right) {
                return right.compareTo(left);
            }
        };
        int cnt = nums.length;
        maxHeap = new PriorityQueue(cnt, revCmp);
        minHeap = new PriorityQueue(cnt);
        int[] ans = new int[cnt];
        for (int i = 0; i  minHeap.peek()) {
                Integer maxHeapRoot = maxHeap.poll();
                Integer minHeapRoot = minHeap.poll();
                maxHeap.add(minHeapRoot);
                minHeap.add(maxHeapRoot);
            }
        }
        else {
            minHeap.add(maxHeap.poll());
        }
        numOfElements++;
    }
    int getMedian() {
        return maxHeap.peek();
    }
}

一道題拿出來, 先想比較直白的解法. 而後看看這個算法哪裏有重複計算, 而後從時間複雜度上, 看看能不能優化.

Sliding Window Median

cs3k.com

Given an array of n integer, and a moving window(size k), move the window at each iteration from the start of the array, find the median of the element inside the window at each moving. (If there are even numbers in the array, return the N/2-th number after sorting the element in the window. )

// TreeMap Version
import java.util.*;


public class Solution {
    /**
     * @param nums
     *            : A list of integers.
     * @return: The median of the element inside the window at each moving.
     */
    public  ArrayList medianSlidingWindow(int[] nums, int k) {
        int n = nums.length;
        TreeSet minheap = new TreeSet();
        TreeSet maxheap = new TreeSet();
        ArrayList result = new ArrayList ();
        
        if (k == 0)
            return result;
        
        int half = (k+1)/2;
        for(int i=0; i<k-1; i++) {
            add(minheap, maxheap, half, new Node(i, nums[i]));
        }
        for(int i=k-1; i<n; i++) {
            add(minheap, maxheap, half, new Node(i, nums[i]));
            result.add(minheap.last().val);
            remove(minheap,maxheap, new Node(i-k+1, nums[i-k+1]));
        }
        return result;
    }
    
    void add(TreeSetminheap, TreeSet maxheap, int size, Node node) {
        if (minheap.size()0 && minheap.last().val>maxheap.first().val) {
                Node s = minheap.last();
                Node b = maxheap.first();
                minheap.remove(s);
                maxheap.remove(b);
                minheap.add(b);
                maxheap.add(s);
            }
        }
    }
    
    void remove(TreeSetminheap, TreeSet maxheap, Node node) {
        if (minheap.contains(node)) {
            minheap.remove(node);
        }
        else {
            maxheap.remove(node);
        }
    }
}

class Node implements Comparable{
    int id;
    int val;
    Node(int id, int val) {
        this.id = id;
        this.val = val;
    }
    public int compareTo(Node other) {
        Node a =(Node)other;
        if (this.val == a.val) {
            return this.id - a.id;
        }
        return this.val - a.val;
    }
}


// Normal heap Version
public class Solution {
    /**
     * @param nums: A list of integers.
     * @return: The median of the element inside the window at each moving.
     */
    public ArrayList medianSlidingWindow(int[] nums, int k) {
        // write your code here
        ArrayList result = new ArrayList();
        int size = nums.length;
        if (size == 0 || size < k) {
            return result;
        }

        PriorityQueue minPQ = new PriorityQueue();
        PriorityQueue maxPQ = new PriorityQueue(11, Collections.reverseOrder());

        int median = nums[0];
        int j = 0;
        if (k == 1) {
            result.add(median);
        }

        for (int i = 1; i  median) {
                minPQ.offer(nums[i]);
            } else {
                maxPQ.offer(nums[i]);
            }

            if (i > k - 1) {
                if (nums[j] > median) {
                    minPQ.remove(nums[j]);
                } else if (nums[j]  maxPQ.size() ? minPQ.poll() : maxPQ.poll();
            } else {
                while (minPQ.size() >= maxPQ.size() + 2) {
                    maxPQ.offer(median);
                    median = minPQ.poll();
                }
                while (maxPQ.size() >= minPQ.size() + 1) {
                    minPQ.offer(median);
                    median = maxPQ.poll();
                }
            }
            if (i >= k - 1) {
                result.add(median);
            }
        }

        return result;
    }
}

// Hash Heap Version
import java.util.*;

class HashHeap {
    ArrayList heap;
    String mode;
    int size_t;
    HashMap hash;

    class Node {
        public Integer id;
        public Integer num;

        Node(Node now) {
            id = now.id;
            num = now.num;
        }

        Node(Integer first, Integer second) {
            this.id = first;
            this.num = second;
        }
    }

    public HashHeap(String mod) {
        // TODO Auto-generated constructor stub
        heap = new ArrayList();
        mode = mod;
        hash = new HashMap();
        size_t = 0;
    }

    int peak() {
        return heap.get(0);
    }

    int size() {
        return size_t;
    }

    Boolean empty() {
        return (heap.size() == 0);
    }

    int parent(int id) {
        if (id == 0) {
            return -1;
        }
        return (id - 1) / 2;
    }

    int lson(int id) {
        return id * 2 + 1;
    }

    int rson(int id) {
        return id * 2 + 2;
    }

    boolean comparesmall(int a, int b) {
        if (a  0) {
                siftdown(0);
            }
        } else {
            hash.put(now, new Node(0, hashnow.num - 1));
        }
        return now;
    }

    void add(int now) {
        size_t++;
        if (hash.containsKey(now)) {
            Node hashnow = hash.get(now);
            hash.put(now, new Node(hashnow.id, hashnow.num + 1));

        } else {
            heap.add(now);
            hash.put(now, new Node(heap.size() - 1, 1));
        }

        siftup(heap.size() - 1);
    }

    void delete(int now) {
        size_t--;
        ;
        Node hashnow = hash.get(now);
        int id = hashnow.id;
        int num = hashnow.num; 
        if (hashnow.num == 1) {

            swap(id, heap.size() - 1);
            hash.remove(now);
            heap.remove(heap.size() - 1);
            if (heap.size() > id) {
                siftup(id);
                siftdown(id);
            }
        } else {
            hash.put(now, new Node(id, num - 1));
        }
    }

    void siftup(int id) {
        while (parent(id) > -1) {
            int parentId = parent(id);
            if (comparesmall(heap.get(parentId), heap.get(id)) == true) {
                break;
            } else {
                swap(id, parentId);
            }
            id = parentId;
        }
    }

    void siftdown(int id) {
        while (lson(id) = heap.size() || (comparesmall(heap.get(leftId), heap.get(rightId)) == true)) {
                son = leftId;
            } else {
                son = rightId;
            }
            if (comparesmall(heap.get(id), heap.get(son)) == true) {
                break;
            } else {
                swap(id, son);
            }
            id = son;
        }
    }
}

public class Solution {
    /**
     * @param nums
     *            : A list of integers.
     * @return: The median of the element inside the window at each moving.
     */
    public ArrayList medianSlidingWindow(int[] nums, int k) {
        // write your code here

        ArrayList ans = new ArrayList();
        if (nums.length == 0)
            return ans;
        int median = nums[0];
        HashHeap minheap = new HashHeap("min");
        HashHeap maxheap = new HashHeap("max");
        for (int i = 0; i  median) {
                    minheap.add(nums[i]);
                } else {
                    maxheap.add(nums[i]);
                }
            }

            if (i >= k) {
                if (median == nums[i - k]) {
                    if (maxheap.size() > 0) {
                        median = maxheap.poll();
                    } else if (minheap.size() > 0) {
                        median = minheap.poll();
                    } 

                } else if (median  minheap.size()) {
                minheap.add(median);
                median = maxheap.poll();
            }
            while (minheap.size() > maxheap.size() + 1) {
                maxheap.add(median);
                median = minheap.poll();
            }

            if (i + 1 >= k) {
                ans.add(median);
            }
        }
        return ans;
    }
}

已知窗口大小, 求中間元素分兩步:

  1. 加一個
  2. 減一個

帶刪除操做Heap:

1. priority_queue (Java) / Heapq (Python)
2. HashHeap
3. 能夠用代替TreeSet(JAVA) vs Set(C++)

c++的set是基於紅黑樹的tree set, 能夠在logn的時間進行add/remove/min/max操做.
tree set能夠代替heap, 可是數據結構不是越牛逼越好, 夠用順手就行, 不須要殺雞用牛刀, 因此heap比較經常使用.

堆能夠用來維護某種流動的集合, 不一樣於僅僅記錄最大最小值.

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.

用stack保存當下的最小值信息, stack能夠用來保存有效信息

// version 1:
public class MinStack {
    private Stack stack;
    private Stack minStack;
    
    public MinStack() {
        stack = new Stack();
        minStack = new Stack();
    }

    public void push(int number) {
        stack.push(number);
        if (minStack.isEmpty()) {
            minStack.push(number);
        } else {
            minStack.push(Math.min(number, minStack.peek()));
        }
    }

    public int pop() {
        minStack.pop();
        return stack.pop();
    }

    public int min() {
        return minStack.peek();
    }
}

// version 2, save more space. but space complexity doesn't change.
public class MinStack {
    private Stack stack;
    private Stack minStack;

    public MinStack() {
        stack = new Stack();
        minStack = new Stack();
    }

    public void push(int number) {
        stack.push(number);
        if (minStack.empty() == true)
            minStack.push(number);
        else {
        // 這裏考慮的相等的狀況也會繼續push
        if (minStack.peek() >= number)
            minStack.push(number);
        }
    }

    public int pop() {
        if (stack.peek().equals(minStack.peek()) ) 
            minStack.pop();
        return stack.pop();
    }

    public int min() {
        return minStack.peek();
    }
}

Implement Queue by Two Stacks

cs3k.com

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.

public class MyQueue {
    private Stack stack1;
    private Stack stack2;

    public MyQueue() {
       // do initialization if necessary
       stack1 = new Stack();
       stack2 = new Stack();
    }
    
    private void stack2ToStack1(){
        while(! stack2.isEmpty()){
            stack1.push(stack2.pop());
        }
    }
    
    public void push(int element) {
        // write your code here
        stack2.push(element);
    }

    public int pop() {
        // write your code here
        if(stack1.empty() == true){
            this.stack2ToStack1();
        }
        return stack1.pop();
    }

    public int top() {
        // write your code here
        if(stack1.empty() == true){
            this.stack2ToStack1();
        }
        return stack1.peek();
    }
}

Expression Expand

cs3k.com

Given an expression s includes numbers, letters and brackets. Number represents the number of repetitions inside the brackets(can be a string or another expression).Please expand expression to be a string.

棧優化dfs,變成非遞歸

全部的遞歸操做, 其實都是用內存的大約32M的棧空間, 如圖:

__________
| dfs i=0  |        
 __________
| dfs i=1  |
 __________
| dfs i=2  |
 __________
| dfs i=3  |
 __________
| dfs i=4  |
 __________
| dfs i=5  |
 __________
| dfs i=6  |
 __________
| dfs i=7  |
 __________
| dfs i=8  |
 __________
| dfs i=9  |
 __________

遞歸操做是從0到1,到2, 到3…到9以後從9回到8,回到7…回到0
若是i特別大的時候, 好比i = 10^10, 就會stack over flow
因此咱們不用內存自帶的棧, 而本身創建棧. 本身創建的棧存儲在硬盤上,能裝下好大的.

遞歸變非遞歸,只能用棧.

// version 1: Stack
public class Solution {
  /**
   * @param s  an expression includes numbers, letters and brackets
   * @return a string
   */
  public String expressionExpand(String s) {
      Stack stack = new Stack();
      int number = 0;
      
      for (char c : s.toCharArray()) {
          if (Character.isDigit(c)) {
              number = number * 10 + c - '0';
          } else if (c == '[') {
              stack.push(Integer.valueOf(number));
              number = 0;
          } else if (c == ']') {
              String newStr = popStack(stack);
              Integer count = (Integer) stack.pop();
              for (int i = 0; i < count; i++) {
                  stack.push(newStr);
              }
          } else {
              stack.push(String.valueOf(c));
          }
      }
      
      return popStack(stack);
  }
  
  private String popStack(Stack stack) {
      // pop stack until get a number or empty
      Stack buffer = new Stack();
      while (!stack.isEmpty() && (stack.peek() instanceof String)) {
          buffer.push((String) stack.pop());
      }
      
      StringBuilder sb = new StringBuilder();
      while (!buffer.isEmpty()) {
          sb.append(buffer.pop());
      }
      return sb.toString();
  }
}

// version 2: Recursion
public class Solution {
  /**
   * @param s  an expression includes numbers, letters and brackets
   * @return a string
   */
  public String expressionExpand(String s) {
      int number = 0;
      int paren = 0;
      String subString = "";
      StringBuilder sb = new StringBuilder(); 
      
      for (char c : s.toCharArray()) {
          if (c == '[') {
              if (paren > 0) {
                  subString = subString + c;
              }
              paren++;
          } else if (c == ']') {
              paren--;
              if (paren == 0) {
                  // push number * substring to sb
                  String expandedString = expressionExpand(subString);
                  for (int i = 0; i = '0' && c <= '9') {
              if (paren == 0) {
                  number = number * 10 + c - '0';
              } else {
                  subString = subString + c;
              }
          } else {
              if (paren == 0) {
                  sb.append(String.valueOf(c));
              } else {
                  subString = subString + c;
              }
          }
      }
      
      return sb.toString();
  }
}

由於stack 的push, pop, top操做都是O(1)的, 因此這道題時間複雜度爲線性的O(n)

Largest Rectangle in Histogram

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

enter image description here

The largest rectangle is shown in the shaded area, which has area = 10 unit.
這道題容易想到的解法是:

for i = 0 -> n
     for j = i -> n
        minh
        minh * (j - i + 1)

O(n^2)枚舉全部可能性
咱們算矩形面積, 須要三個值: 起點, 終點 , 高
咱們以前是枚舉起點, 終點; 如今咱們能不能枚舉高呢?
能夠哇:

高度      終點-起點
 2     *     1
 1     *     6
 5     *     2
 6     *     1
 2     *     4
 3     *     1

這種作法的本質是枚舉高度, 而後找出這個高度能夠向左右延伸出去多寬.
而向左右延伸出去多寬, 實際上是找左右第一個比它小的數字, 這就很適合單調棧啦?
enter image description here
拿這道題來講, stack先放2進去, 左邊是示意, 加入的是存的元素, 以便理解. 真正用的是右邊, 棧裏面存的是index

S:  2            S: 0

2左邊木有更小的, 右邊不知道, 就先用棧來保存當下狀況有用的信息
下一個是1, 比2小, 2右邊已經找到第一個比它小的了, pop出來, 而後S爲空

S:               S:

把 1塞到stack裏面, 1左邊木有比它小的, 右邊不知道, 塞棧裏面等着:

S:  1            S: 1

下一個數是5, 5比1大, 仍是不知道1右邊比它小的是誰. 5知道左邊比它小的是1, 右邊也不知道, 塞着

S : 1 5          S: 1 2

6和5狀況相似, 塞着:

S : 1 5 6        S: 1 2 3

下一個是2, 2<6 啊. 6 右面的第一個比它小的找到了, 6的信息全了, 把6踢掉:

S: 1 5           S: 1 2

2<5啊,5的信息也全了, 把5也踢掉

S : 1            S: 1

把2 塞棧裏面:

S: 1 2           S: 1 4

下一個是3, 比2大, 塞進去

S: 1 2 3         S: 1 4 5

爲了讓棧的元素都能蹦出來, 因此最後加一個負無窮, 頭上也能夠放一個負無窮
O(n)的時間複雜度, stack存的是座標

public class Solution {
    public int largestRectangleArea(int[] height) {
        if (height == null || height.length == 0) {
            return 0;
        }
        
        Stack stack = new Stack();
        int max = 0;
        for (int i = 0; i <= height.length; i++) {
            int curt = (i == height.length) ? -1 : height[i];
            while (!stack.isEmpty() && curt <= height[stack.peek()]) {
                int h = height[stack.pop()];
                int w = stack.isEmpty() ? i : i - stack.peek() - 1;
                max = Math.max(max, h * w);
            }
            stack.push(i);
        }
        
        return max;
    }
}

Max Tree

Given an integer array with no duplicates. A max tree building on this array is defined as follow:

The root is the maximum number in the array
The left subtree and right subtree are the max trees of the subarray divided by the root number.
Construct the max tree by the given array.

Given [2, 5, 6, 0, 3, 1], the max tree constructed by this array is:

6
   / \
  5   3
 /   / \
2   0   1

很容易想到的解法是:
在[2, 5, 6, 0, 3, 1]中選出最大的6, 而後遞歸它左面的[2,5], 再遞歸它右面的[0,3,1]
是由上向下建樹, 時間複雜度是NlogN. 遇到[6,5,4,3,2,1]這種狀況, 會變成最差NN

咱們如今自下而上建樹, 找每一個節點的父親節點:
父親節點的標準是左右第一個比它大的中比較小的那個是它的父親節點

public class Solution {
  /**
   * @param A
   *            : Given an integer array with no duplicates.
   * @return: The root of max tree.
   */
  public static TreeNode maxTree(int[] A) {
    // write your code here
    Stack stack = new Stack();
    TreeNode root = null;
    for (int i = 0; i  stack.peek().val) {
          TreeNode nodeNow = stack.pop();
          if (stack.isEmpty()) {
            right.left = nodeNow;
          } else {
            TreeNode left = stack.peek();
            if (left.val > right.val) {
              right.left = nodeNow;
            } else {
              left.right = nodeNow;
            }
          }
        } else
          break;
      }
      stack.push(right);
    }
    return stack.peek().left;
  }
}
/**
 * Definition of TreeNode:
 * public class TreeNode {
 *     public int val;
 *     public TreeNode left, right;
 *     public TreeNode(int val) {
 *         this.val = val;
 *         this.left = this.right = null;
 *     }
 * }
 */
public class Solution {
    /**
     * @param A: Given an integer array with no duplicates.
     * @return: The root of max tree.
     */
    public TreeNode maxTree(int[] A) {
        // write your code here
        int len = A.length;
        TreeNode[] stk = new TreeNode[len];
        for (int i = 0; i < len; ++i)
            stk[i] = new TreeNode(0);
        int cnt = 0;
        for (int i = 0; i  0 && A[i] > stk[cnt-1].val) {
                tmp.left = stk[cnt-1];
                cnt --;
            }
            if (cnt > 0)
                stk[cnt - 1].right = tmp;
            stk[cnt++] = tmp;
        }
        return stk[0];
    }
}

總結:

1. Heap:求集合的最大值
2. Stack: 
   單調棧的運用找左邊和右邊第一個比它大的元素
   遞歸轉非遞歸
3. Windows problem
   加一個數
   刪一個數的方法

Hash Heap

線段樹是一個二叉樹結構, 它能作全部heap能作的操做,而且能夠logn時間查找到某個區間的最大和最小值

Heap        Segment Tree
        push    O(logn)        O(logn)
        pop     O(logn)        O(logn)
        top     O(1)           O(1)
        modify   X             O(logn)

爲了弄明白hash heap,咱們先須要瞭解heap的三個操做和原理

heap的操做

  1. push O(logn)
  2. pop O(logn)
  3. top O(1)
    heap的本質是二叉樹, 而且知足任意父親節點>(max heap)或<(min heap)它的左右子孫

heap的原理

cs3k.com

HEAP的存儲

  1. heap是二叉樹, 能夠從上到下,從左到右把節點存在數組裏
    好比:

     

    3
               /   \
            17        5
           / \        / \
        19  21       7    6 
    
           
         數組:    [3, 17, 5, 19, 21, 7, 6]
         ID:      [0, 1, 2,  3,  4, 5, 6]

一個節點的父親和左右兒子在數組中的位置計算:

parent    = (ID - 1)/2
    left_son  = ID * 2 + 1
    right_son = ID * 2 + 2

HEAP的PUSH操做

假設如今有:

3
              /   \
           17        5
          / \        / 
       19  21       7    

           
         數組:    [3, 17, 5, 19, 21, 7]
         ID:      [0, 1, 2,  3,  4, 5]

我想加0進去:

  1. 0節點加入樹的最底層的最右邊,數組末尾也加上0
    3
               /   \
            17        5
           / \        / \
        19  21       7    0 
    
           
         數組:    [3, 17, 5, 19, 21, 7, 0]
         ID:      [0, 1, 2,  3,  4, 5, 6]
  2. sift up :把插入的0節點較依次向上調整到合適位置知足堆的性質。
  • sift up 1:0節點和5節點對調,數組中swap(0,5)的位置
    3
               /   \
            17        0
           / \        / \
        19  21       7    5 
    
           
         數組:    [3, 17, 0, 19, 21, 7, 5]
         ID:      [0, 1,  2, 3,  4, 5, 6]
  • sift up 2:0節點和3節點對調,數組中swap(0,3)的位置
    0
               /   \
            17        3
           / \        / \
        19  21       7    5 
    
           
         數組:    [0, 17, 3, 19, 21, 7, 5]
         ID:      [0, 1,  2, 3,  4, 5, 6]

HEAP的POP操做

0
              /   \
           17        3
          / \        / \
       19  21       7   5

           
         數組:    [0, 17, 3, 19, 21, 7, 5]
         ID:      [0, 1, 2,  3,  4, 5, 6]

在上述heap裏面:

  1. 最頂點0和最後一行的最右邊節點5對調,數組0和5 swap。
    5
               /   \
            17        3
           / \        / \
        19  21       7   0
    
           
         數組:    [5, 17, 3, 19, 21, 7, 0]
         ID:      [0, 1, 2,  3,  4, 5, 6]
  2. 刪掉最末尾的節點和數組的數字。對調刪末尾而不直接從中間刪除的緣由是末尾刪掉就行,很簡單, 前面刪掉還要諾數組, 麻煩。
    5
               /   \
            17        3
           / \        / 
        19  21       7   
    
           
         數組:    [5, 17, 3, 19, 21, 7]
         ID:      [0, 1, 2,  3,  4, 5]
  3. sift down:不斷和下面比,知道知足堆得性質:
  • sift down 1:5節點和3節點對調,數組中swap(0,5)的位置
    3
                /   \
             17        5
            / \        / 
         19  21       7   
    
           
         數組:    [3, 17, 5, 19, 21, 7]
         ID:      [0, 1, 2,  3,  4, 5]

HEAP的TOP操做

直接取最上面的節點,數組中的第一個數就行。

Hash Heap

cs3k.com

hash heap和heap不同的地方是:

多了個hash,嘿嘿嘿(寫到這兒我本身傻笑了一分鐘,哈哈哈哈哈)
這個hash呢, 是用來進行一個hash不同的操做, 即:

定點刪除: 定點刪除與普通的pop不一樣,普通的pop是根據大小,而定點刪除是指哪一個數,刪哪一個數。好比以下heap裏面我想刪除20這個數:

3
              /   \
           17        5
          / \        / 
       20  21       7   

           
         數組:    [3, 17, 5, 20, 21, 7]
         ID:      [0, 1, 2,  3,  4, 5]

java裏面呢, 有一個remove操做能夠定點刪除,時間複雜度是O(n), 它怎麼搞的呢:

  1. for循環整個數組, 查詢找到20這個數
  2. delete
  • 20和7換
  • 刪最後面的20的節點
  • 根據不一樣狀況sift up/sift down

刪除之因此須要O(n)的時間比較慢是由於查詢這步須要O(n)的時間, 說到快速查找,咱們要想到哈希。
因此咱們要創建一個哈希表,鏈接到二叉樹的節點上,而後刪除20的操做就變成:
1.用hash查找20
2.刪掉20 ,數組和hash裏的20都刪掉。7的指針改一下
3.siftup/sift down

class HashHeap {
    ArrayList<Integer> heap;
    String mode;
    int size_t;
    HashMap<Integer, Node> hash;

    class Node {
        public Integer id;
        public Integer num;

        Node(Node now) {
            id = now.id;
            num = now.num;
        }

        Node(Integer first, Integer second) {

            this.id = first;
            this.num = second;
        }
    }

    public HashHeap(String mod) { // 傳入min 表示最小堆,max 表示最大堆
        // TODO Auto-generated constructor stub
        heap = new ArrayList<Integer>();
        mode = mod;
        hash = new HashMap<Integer, Node>();
        size_t = 0;
    }

    int peak() {
        return heap.get(0);
    }

    int size() {
        return size_t;
    }

    Boolean empty() {
        return (heap.size() == 0);
    }

    int parent(int id) {
        if (id == 0) {
            return -1;
        }
        return (id - 1) / 2;
    }

    int lson(int id) {
        return id * 2 + 1;
    }

    int rson(int id) {
        return id * 2 + 2;
    }

    boolean comparesmall(int a, int b) {
        if (a <= b) {
            if (mode == "min")
                return true;
            else
                return false;
        } else {
            if (mode == "min")
                return false;
            else
                return true;
        }

    }

    void swap(int idA, int idB) {
        int valA = heap.get(idA);
        int valB = heap.get(idB);

        int numA = hash.get(valA).num;
        int numB = hash.get(valB).num;
        hash.put(valB, new Node(idA, numB));
        hash.put(valA, new Node(idB, numA));
        heap.set(idA, valB);
        heap.set(idB, valA);
    }

    Integer poll() {
        size_t--;
        Integer now = heap.get(0);
        Node hashnow = hash.get(now);
        if (hashnow.num == 1) {
            swap(0, heap.size() - 1);
            hash.remove(now);
            heap.remove(heap.size() - 1);
            if (heap.size() > 0) {
                siftdown(0);
            }
        } else {
            hash.put(now, new Node(0, hashnow.num - 1));
        }
        return now;
    }

    void add(int now) {
        size_t++;
        if (hash.containsKey(now)) {
            Node hashnow = hash.get(now);
            hash.put(now, new Node(hashnow.id, hashnow.num + 1));

        } else {
            heap.add(now);
            hash.put(now, new Node(heap.size() - 1, 1));
        }

        siftup(heap.size() - 1);
    }

    void delete(int now) {
        size_t--;
        ;
        Node hashnow = hash.get(now);
        int id = hashnow.id;
        int num = hashnow.num;
        if (hashnow.num == 1) {

            swap(id, heap.size() - 1);
            hash.remove(now);
            heap.remove(heap.size() - 1);
            if (heap.size() > id) {
                siftup(id);
                siftdown(id);
            }
        } else {
            hash.put(now, new Node(id, num - 1));
        }
    }

    void siftup(int id) {
        while (parent(id) > -1) {
            int parentId = parent(id);
            if (comparesmall(heap.get(parentId), heap.get(id)) == true) {
                break;
            } else {
                swap(id, parentId);
            }
            id = parentId;
        }
    }

    void siftdown(int id) {
        while (lson(id) < heap.size()) {
            int leftId = lson(id);
            int rightId = rson(id);
            int son;
            if (rightId >= heap.size() || (comparesmall(heap.get(leftId), heap.get(rightId)) == true)) {
                son = leftId;
            } else {
                son = rightId;
            }
            if (comparesmall(heap.get(id), heap.get(son)) == true) {
                break;
            } else {
                swap(id, son);
            }
            id = son;
        }
    }
}

hash heap和linked hash很像。 在linked hash裏, hash指向記錄雙向鏈表的節點, 用來快速找到要加/刪的點。

hash heap不常常用,能夠被替代,替代品有如下:

  1. java: tree map/ tree set
  2. c++: map/ set
  3. python: ordered
  4. dictionary

    cs3k.com

相關文章
相關標籤/搜索