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

灌水问题,要确定基调柱子,就是水桶的桶边。
对于每个位置来说,重要的是:
它左边的所有柱子的最大值a
它右边的所有柱子的最大值b
a和b中比较小的那个和它自己的差值是这个柱子能灌的水量!
奶奶的,我开始绕了好久没反应过来,快要怀疑自己的智商了。。。老师还说这是一道很简单的题。。。阿西巴。。。
这里取网上http://blog.csdn.net/wzy_1988/article/details/17752809 的理解:
首先,碰到这样的题目不要慌张,挨个分析每个A[i]能trapped water的容量,然后将所有的A[i]的trapped water容量相加即可

其次,对于每个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了。

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 here

一个墙头在被灌完水之后, 就要把它更新为已路过的最高墙头,所以现在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 here

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 here

现在的墙头里面最矮的是第二个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如下:

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

转载于:https://www.cnblogs.com/jiuzhangsuanfa/p/9895682.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值