九章算法高级班笔记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

相关文章
相关标签/搜索