九章算法高級班筆記4.二分法深刻和掃描線

  1. 二分法的深刻理解
  • 二維二分
  • 按照值域進行二分
  1. Stack
  • 掃描線

3.雙端隊列html

 

Find Peak Element II cs3k.com

There is an integer matrix which has the following features:java

 

The numbers in adjacent positions are different.
The matrix has n rows and m columns.
For all i < m, A[0][i] < A[1][i] && A[n – 2][i] > A[n – 1][i].
For all j < n, A[j][0] < A[j][1] && A[j][m – 2] > A[j][m – 1].
We define a position P is a peek if:c++

A[j][i] > A[j+1][i] && A[j][i] > A[j-1][i] && A[j][i] > A[j][i+1] && A[j][i] > A[j][i-1]
Find a peak element in this matrix. Return the index of the peak.git

Noticegithub

The matrix may contains multiple peeks, find any of them.面試

Have you met this question in a real interview? Yes
Example
Given a matrix:算法

[
[1 ,2 ,3 ,6 ,5],
[16,41,23,22,6],
[15,17,24,21,7],
[14,18,19,20,10],
[13,14,11,10,9]
]
return index of 41 (which is [1,1]) or index of 24 (which is [2,2])windows

這道題我想到的是隨便找個位置當起點,而後往高處爬.
我開始以爲這種算法的時間複雜度爲m+n, 後來以爲本身有點腦殘...
這種作法最壞狀況下的時間複雜度爲0(n^2).
狀況以下:數組

XXXXXXXXXXXXXXXXXXXXXXXXXXXXX
            X 1 2 3 4 5 6 7 8 9 10 11XXXX
            X 19 18 17 16 15 14 13 12XXXX
            X 20 21 22 23 24 25 26 27XXXX
            ......

呈蛇形排列的狀況.數據結構

而後呢咱們想一想能不能相似於find peak I 裏面的二分去掉一半?
咱們首先改一下栗子...改爲以下,方便理解:

[ 1,  2,  3,  6,  5],
  [16, 41, 23, 22,  6],
  [15, 17, 16, 21,  7],
  [14, 18, 19, 20, 10],
  [13, 14, 11, 10,  9]

咱們先假設取中間的一行:

[15, 17, 16, 21,  7]

中間一行有兩個極大值:17和21, 咱們目標是:看看找到極值,而後極值往高了爬,能不能甩掉身後小的那半邊矩陣, 並保證在剩下的通常裏仍然有要找的答案.
假設咱們先選擇17,而後上下找比它大的,意圖刪掉帶有17的那半邊:
若是走到41,是ok的...
可是若是走到18呢,18->19->20->21,又走回原來的第3行了...阿西吧,行不通.

而後咱們試試看看最大值,21呢?由於21是第三行的最大值, 因此咱們若是找個一個比21大的, 就是第二行第四列的22. 咱們發現, 若是從21走到22, 在從22找比它大的往高爬, 永遠都不會爬回21所在的第三行, 因此我成功甩掉了第四五行…下面咱們來肯定在剩下的一半里面, 必定有咱們要找的答案:
題目中的條件:

For all i < m, A[0][i] < A[1][i] && A[n – 2][i] > A[n – 1][i].
For all j < n, A[j][0] < A[j][1] && A[j][m – 2] > A[j][m – 1].

能夠保證四周的一圈是山腳,比中間的要小, 這樣才能肯定中間必定有個極值峯, 如pic4.1圖左, 當刪掉21所在的第三行下面的第四五行, 圖變成了pic4.1的右圖形式,保證了峯值依然存在:

Evernote Snapshot 20171029 190922

class Solution {
    /**
     * @param A: An integer matrix
     * @return: The index of the peak
     */
    public List find(int x1, int x2, int y1, int y2,
                              int[][] A, boolean flag) {
        
        if (flag) {
            int mid = x1 + (x2 - x1) / 2;
            int index = y1;
            for (int i = y1; i  A[mid][index])
                    index = i;
                    
            if (A[mid - 1][index] > A[mid][index])
                return find(x1, mid - 1, y1, y2, A, !flag);
            else if (A[mid + 1][index] > A[mid][index])
                return find(mid + 1, x2, y1, y2, A, !flag);
            else
                return new ArrayList(Arrays.asList(mid, index));
        } else {
            int mid = y1 + (y2 - y1) / 2;
            int index = x1;
            for (int i = x1; i  A[index][mid])
                    index = i;
                    
            if (A[index][mid - 1] > A[index][mid])
                return find(x1, x2, y1, mid - 1, A, !flag);
            else if (A[index][mid + 1] > A[index][mid])
                return find(x1, x2, mid + 1, y2, A, !flag);
            else
                return new ArrayList(Arrays.asList(index, mid));
        }
    }
    public List findPeakII(int[][] A) {
        // write your code here
        int n = A.length;
        int m = A[0].length;
        return find(1, n - 2, 1, m - 2, A, true);
    }
}

這道題的時間複雜度是:
T(n) = O(n) + O(n/2) + T(n/2)
最後的時間仍是O(3n), 即爲O(n).

二分法之二分答案 Binary Search on Result

cs3k.com

每每沒有給你一個數組讓你二分, 而是找到知足某個條件的最大或者最小值
由於找答案有兩種思路:

  1. 比較常見的是直接求, 咱們通常都是會想到這個方法
  2. 答案若是有二分性的話, 咱們能夠猜答案, 而後驗證.

而神馬是有二分性呢:
就是一個區間, 中間某個點畫條線, 線一邊的所有知足條件, 另外一邊的所有不知足:

->  知足   ->|           
            |____________|____________|

解題方法:

經過猜值判斷是否知足題意不對去搜索可能解

  1. 找到可行解範圍
  2. 猜答案
  3. 檢驗條件
  4. 調整搜索範圍

模板以下:

enter image description here

Sqrt(x)

cs3k.com

Implement int sqrt(int x).

Compute and return the square root of x.

Have you met this question in a real interview? Yes
Example
sqrt(3) = 1

sqrt(4) = 2

sqrt(5) = 2

sqrt(10) = 3

class Solution {
    /**
     * @param x: An integer
     * @return: The sqrt of x
     */
    public int sqrt(int x) {
        // find the last number which square of it <= x
        long start = 1, end = x;
        while (start + 1 < end) {
            long mid = start + (end - start) / 2;
            if (mid * mid <= x) {
                start = mid;
            } else {
                end = mid;
            }
        }
        
        if (end * end <= x) {
            return (int) end;
        }
        return (int) start;
    }
}

Sqrt(x) II

cs3k.com

Implement double sqrt(double x) and x >= 0.

Compute and return the square root of x.

Notice

You do not care about the accuracy of the result, we will help you to output results.

Have you met this question in a real interview? Yes
Example
Given n = 2 return 1.41421356

這道題有個浮點精度問題, 在計算機裏面, 兩個樹若是不相等,可是由於精度問題, 計算機會判斷他們相等. 計算機如何判斷兩個浮點數y和z相等呢?
if (y ==z) 對於計算機來講是 if (abs(y-z)<eps), 這個eps是計算機設定的很小的值. 那麼對於計算機來講:
y = 1.0
z = 1.000000000000000000000000000001
是相等的, 雖然他們原本是不相等的.

這道題的eps腫麼肯定呢?

答案就是: 和麪試官商量…

public class Solution {
    /**
     * @param x a double
     * @return the square root of x
     */
    public double sqrt(double x) {
        // Write your code here
        double left = 0.0;
        double right = x;
        double eps = 1e-12;

        if(right  eps) {
            // 二分浮點數 和二分整數不一樣
            // 通常都有一個精度的要求 譬如這題就是要求小數點後八位
            // 也就是隻要咱們二分的結果達到了這個精度的要求就能夠
            // 因此 須要讓 right 和 left 小於一個咱們事先設定好的精度值 eps
            // 通常eps的設定1e-8,由於這題的要求是到1e-8,因此我把精度調到了1e-12
            // 最後 選擇 left 或 right 做爲一個結果便可 
            double mid = (right + left) / 2;
            if(mid * mid < x) {
                left = mid;
            }
            else {
                right = mid;
            }
        }

        return left;
    }
}

Wood Cut

cs3k.com

Given n pieces of wood with length L[i] (integer array). Cut them into small pieces to guarantee you could have equal or more than k pieces with the same length. What is the longest length you can get from the n pieces of wood? Given L & k, return the maximum length of the small pieces.

Notice

You couldn’t cut wood into float length.

If you couldn’t get >= k pieces, return 0.

Have you met this question in a real interview? Yes
Example
For L=[232, 124, 456], k=7, return 114.

這道題能夠用heap來作, 同時呢, 也能夠二分試答案:

public class Solution {
    /** 
     *@param L: Given n pieces of wood with length L[i]
     *@param k: An integer
     *return: The maximum length of the small pieces.
     */
    public int woodCut(int[] L, int k) {
        int max = 0;
        for (int i = 0; i = k) {
            return end;
        }
        if (count(L, start) >= k) {
            return start;
        }
        return 0;
    }
    
    private int count(int[] L, int length) {
        int sum = 0;
        for (int i = 0; i < L.length; i++) {
            sum += L[i] / length;
        }
        return sum;
    }
}

有個小問題:

這道題若是範圍是[1, INT_MAX]的話, 最多咱們能找多少次呢?
就是log(max -min)次. 那麼這個數大約是多大呢?

31, 由於int是四個byte,就是最多32位, 32位的最大的整數是2^31-1
,因此須要找log(2^31-1-1)次, 就是大約31次. 那麼時間複雜度是多少呢?
log(max-min) * O(n)

Find the Duplicate Number

cs3k.com

Given an array nums containing n + 1 integers where each integer is between 1 and n (inclusive), prove that at least one duplicate number must exist. Assume that there is only one duplicate number, find the duplicate one.

Notice

You must not modify the array (assume the array is read only).
You must use only constant, O(1) extra space.
Your runtime complexity should be less than O(n^2).
There is only one duplicate number in the array, but it could be repeated more than once.
Have you met this question in a real interview? Yes
Example
Given nums = [5,5,4,3,2,1] return 5
Given nums = [5,4,4,3,2,1] return 4

由於一共有n+1個數,因此若是一個一個找的話,對於
[5,5,4,3,2,1] 來講呢?咱們一個一個試:

1的時候  小於等於1的  1個
2的時候  小於等於2的  2個
3的時候  小於等於3的  3個
4的時候  小於等於4的  4個
5的時候  小於等於5的  6個

咱們發現, 對於數字n來講,若是小於等於它的數字個數小於等於n, 那麼它和它以前木有重複的. 反之有重複.

對於這種for的查找, 答案有二分性, 咱們要想到神馬?

!!!二分法

public class Solution {
    /**
     * @param nums an array containing n + 1 integers which is between 1 and n
     * @return the duplicate one
     */
    public int findDuplicate(int[] nums) {
        // Write your code here
        int start = 1;
        int end = nums.length - 1;
        while(start + 1 < end) {
            int mid = start + (end - start) / 2;
            if (check_smaller_num(mid, nums) <= mid) {
                start = mid;
            } else {
                end = mid;
            }
        }
            
        if (check_smaller_num(start, nums) <= start) {
            return end;
        }
        return start;
    }
    
    public int check_smaller_num(int mid, int[] nums) {
        int cnt = 0;
        for(int i = 0; i < nums.length; i++){
            if(nums[i] <= mid){
                cnt++;
            }
        }
        return cnt;
    }
}

// 映射法
public class Solution {
    /**
     * @param nums an array containing n + 1 integers which is between 1 and n
     * @return the duplicate one
     */
    public int findDuplicate(int[] nums) {
        // Write your code here
        if (nums.length <= 1)
            return -1;

        int slow = nums[0];
        int fast = nums[nums[0]];
        while (slow != fast) {
            slow = nums[slow];
            fast = nums[nums[fast]];
        }

        fast = 0;
        while (fast != slow) {
            fast = nums[fast];
            slow = nums[slow];
        }
        return slow;
    }
}

神馬狀況不用二分呢?

知足條件和不知足條件的混合:

-> 不知足->知足           
            |____________|____________|

Sweep-Line

cs3k.com

Number of Airplanes in the Sky

Given an interval list which are flying and landing time of the flight. How many airplanes are on the sky at most?

Notice

If landing and flying happens at the same time, we consider landing should happen at first.

Have you met this question in a real interview? Yes
Example
For interval list

[
[1,10],
[2,3],
[5,8],
[4,7]
]

這道題呢, 和起點終點的大小有關.
可是有個問題, 若是按起點排序呢, 終點無法處理.

若是按終點排序呢, 起點又無法處理.

區間類的問題, 咱們要想掃描線.

這道題咱們能夠怎麼作?
for一遍1 2 3 4 5 6 7 8 9 10的時間段, 找當時天空中一共有幾架飛機, 而後取最大
本質是這樣的, 如圖4.2所示:

Evernote Snapshot 20171029 204132

咱們有個時間軸, 每一個時間咱們畫一條鉛筆的總線, 看各個時間的這條總線和橫線的區間交點最可能是幾個.

如今咱們把起點和終點拆開記錄,T表明起飛, F表明降落:

1  T             10  F
        2  T              3  F
        5  T              8  F
        4  T              7  F

排序而後依次遍歷這些時間節點, 記錄count:

1   count++
                2   count++
                3   count--
                ......
class Point{
    int time;
    int flag;

    Point(int t, int s){
      this.time = t;
      this.flag = s;
    }
    public static Comparator PointComparator  = new Comparator(){
      public int compare(Point p1, Point p2){
        if(p1.time == p2.time) return p1.flag - p2.flag;
        else return p1.time - p2.time;
      }
    };
}
  
class Solution {
    /**
     * @param intervals: An interval array
     * @return: Count of airplanes are in the sky.
     */
  public int countOfAirplanes(List airplanes) { 
    List list = new ArrayList(airplanes.size()*2);
    for(Interval i : airplanes){
      list.add(new Point(i.start, 1));
      list.add(new Point(i.end, 0));
    }

    Collections.sort(list,Point.PointComparator );
    int count = 0, ans = 0;
    for(Point p : list){
      if(p.flag == 1) count++;
      else count--;
      ans = Math.max(ans, count);
    }

    return ans;
  }
    
}

Building Outline

cs3k.com

Given N buildings in a x-axis,each building is a rectangle and can be represented by a triple (start, end, height),where start is the start position on x-axis, end is the end position on x-axis and height is the height of the building. Buildings may overlap if you see them from far away,find the outline of them。

An outline can be represented by a triple, (start, end, height), where start is the start position on x-axis of the outline, end is the end position on x-axis and height is the height of the outline.

enter image description here

Notice

Please merge the adjacent outlines if they have the same height and make sure different outlines cant overlap on x-axis.
Example
Given 3 buildings:

[
  [1, 3, 3],
  [2, 4, 4],
  [5, 6, 1]
]

The outlines are:

[
  [1, 2, 3],
  [2, 4, 4],
  [5, 6, 1]
]

圖示參考:
https://briangordon.github.io/2014/08/the-skyline-problem.html

這是一道掃描線和其它結合的題:
區間問題, 若是按起點和終點排序解決不了, 那咱們要想到掃描線. 而掃描線由於須要排序, 因此時間複雜度通常是O(nlogn).

這道題舉個栗子:

[
  [1, 3, 2],
  [2, 4, 3],
  [5, 6, 2]
]

就要拆成:

1     T    2
        3     F    2
        2     T    3
        4     F    3
        5     T    2
        6     F    2

sweep line + heap 由於要刪除, 因此c++要用heap

掃描問題的思路

  1. 事件每每是以區間的形式存在
  2. 區間兩端表明事件的開始和結束
  3. 須要排序
import java.util.*;

public class Solution {

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

    public int peek() {
      return heap.get(0);
    }

    public int size() {
      return size_t;
    }

    public Boolean isEmpty() {
      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;
    }

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

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

  class Edge {
    int pos;
    int height;
    boolean isStart;

    public Edge(int pos, int height, boolean isStart) {
      this.pos = pos;
      this.height = height;
      this.isStart = isStart;
    }

  }

  class EdgeComparator implements Comparator {
    @Override
    public int compare(Edge arg1, Edge arg2) {
      Edge l1 = (Edge) arg1;
      Edge l2 = (Edge) arg2;
      if (l1.pos != l2.pos)
        return compareInteger(l1.pos, l2.pos);
      if (l1.isStart && l2.isStart) {
        return compareInteger(l2.height, l1.height);
      }
      if (!l1.isStart && !l2.isStart) {
        return compareInteger(l1.height, l2.height);
      }
      return l1.isStart ? -1 : 1;
    }

    int compareInteger(int a, int b) {
      return a <= b ? -1 : 1;
    }
  }

  List output(List res) {
    List ans = new ArrayList();
    if (res.size() > 0) {
      int pre = res.get(0).get(0);
      int height = res.get(0).get(1);
      for (int i = 1; i  0) {
          now.add(pre);
          now.add(id);
          now.add(height);
          ans.add(now);
        }
        pre = id;
        height = res.get(i).get(1);
      }
    }
    return ans;
  }

  public List buildingOutline(int[][] buildings) {
    // write your code here
    List res = new ArrayList();

    if (buildings == null || buildings.length == 0
        || buildings[0].length == 0) {
      return res;
    }
    ArrayList edges = new ArrayList();
    for (int[] building : buildings) {
      Edge startEdge = new Edge(building[0], building[2], true);
      edges.add(startEdge);
      Edge endEdge = new Edge(building[1], building[2], false);
      edges.add(endEdge);
    }
    Collections.sort(edges, new EdgeComparator());

    HashHeap heap = new HashHeap("max");

    List now = null;
    for (Edge edge : edges) {
      if (edge.isStart) {
        if (heap.isEmpty() || edge.height > heap.peek()) {
          now = new ArrayList(Arrays.asList(edge.pos,
              edge.height));
          res.add(now);
        }
        heap.add(edge.height);
      } else {
        heap.delete(edge.height);
        if (heap.isEmpty() || edge.height > heap.peek()) {
          if (heap.isEmpty()) {
            now = new ArrayList(Arrays.asList(edge.pos, 0));
          } else {
            now = new ArrayList(Arrays.asList(edge.pos,
                heap.peek()));
          }
          res.add(now);
        }
      }
    }
    return output(res);
  }

}

Sliding Window Maximum

Given an array of n integer with duplicate number, and a moving window(size k), move the window at each iteration from the start of the array, find the maximum number inside the window at each moving.

Have you met this question in a real interview? Yes
Example
For array [1, 2, 7, 7, 8], moving window size k = 3. return [7, 7, 8]

At first the window is at the start of the array like this

[|1, 2, 7| ,7, 8] , return the maximum 7;

then the window move one step forward.

[1, |2, 7 ,7|, 8], return the maximum 7;

then the window move one step forward again.

[1, 2, |7, 7, 8|], return the maximum 8;

打個比方:

cs3k.com

queue: 是吃飯拉屎
stack: 是吃飯有毒吐出來
queue: 是上下都吃 上下都排…

這道題若是用heap作呢, 就是O(nlogk)的時間. 若是優化的話呢?
只能想線性數據結構啦

線性數據結構有啥呢? queue stack 和deque

那咱們先試試stack?

好比: [ 1 2 7 5 8] 這組數據:

咱們想找最大值, 那麼就是若是從左到右, 若是遇到比一個數a大的, 就把a踢出去:
先:

1        1加入
 1  2     2加入 
 2        1踢掉
 2  7     7加入
 7        2踢掉
 7  5     5加入

這時候注意了, 5比7 小並不能說明5不會成爲最大值, 由於stack先進後出, 因此有保留有效信息的能力, 因此咱們先把5 存着, 而後:

7  5  8  8加入
 7  8     5踢掉
 8        7踢掉

單調棧升級爲單調雙端隊列:

public class Solution {
    
    /**
     * @param nums: A list of integers.
     * @return: The maximum number inside the window at each moving.
     */
    void inQueue(Deque deque, int num) {
        while (!deque.isEmpty() && deque.peekLast() < num) {
            deque.pollLast();
        }
        deque.offer(num);
    }
    
    void outQueue(Deque deque, int num) {
        if (deque.peekFirst() == num) {
            deque.pollFirst();
        }
    }
    
    public ArrayList maxSlidingWindow(int[] nums, int k) {
        // write your code here
        ArrayList ans = new ArrayList();
        Deque deque = new ArrayDeque();
        if (nums.length == 0) {
            return ans;
        }
        for (int i = 0; i < k - 1; i++) {
            inQueue(deque, nums[i]);
        }
        
        for(int i = k - 1; i < nums.length; i++) {
            inQueue(deque, nums[i]);
            ans.add(deque.peekFirst());
            outQueue(deque, nums[i - k + 1]);
        }
        return ans;

    }
}
  1. 二分法
  • 按值二分,須要怎麼二分性
  1. 掃描線
  • 見到區間須要排序就能夠考慮掃描線
  1. 雙端隊列
  • 只用掌握sliding windows maximum這一道題目
  • 維護一個候選可能的最大值集合

Debug的基本步驟

cs3k.com

爲何Debug必定要靠本身?
緣由有四:

  1. 若是是別人給你指出你的程序哪兒錯了,你本身不會有任何收穫,你下一次依舊會犯一樣的錯誤。
  2. 通過長時間努力Debug 得到的錯誤,印象更深入。
  3. debug 能力是面試的考察範圍。
  4. 鍛鍊Debug 能力可以提升本身的Bug Free的能力。

Debug的基本步驟

  1. 從新讀一遍程序。按照本身當初想的思路,走一遍程序,看看程序是否是按照本身的思路在走。(由於不少時候,你寫着寫着就忘了不少事兒)這種方式是最有效最快速的 Debug 方式。
  2. 找到一個很是小很是小的可讓你的程序出錯的數據。好比空數組,空串,1-5個數的數組,一個字符的字符串。
  3. 在程序的若干位置輸出一些中間結果。好比排序以後輸出一下,看看是否是真的按照你所想的順序排序的。這樣能夠定位到程序出錯的部分。
  4. 定位了出錯的部分以後,查看本身的程序該部分的邏輯是否有錯。

在第4步中,若是沒法經過肉眼看出錯誤的部分,就一步步「模擬執行」程序,找出錯誤。

實在Debug 不出來怎麼辦?
若是你已經 Debug 了一成天,能夠考慮向他人求助。

cs3k.com

相關文章
相關標籤/搜索