左神直通BAT算法(進階篇)-下

我的技術博客:www.zhenganwen.topjava

單調棧結構

原始問題

給你一個數組,找出數組中每一個數左邊離它最近的比它大的數和右邊離它最近的比它大的數。node

思路:使用一個棧,要求每次元素進棧後要維持棧中從棧底到棧頂元素值是從大到小排列的約定。將數組中的元素依次進棧,若是某次元素進棧後會違反了上述的約定(即該進棧元素比棧頂元素大),就先彈出棧頂元素,並記錄該棧頂元素的信息:git

  • 該元素左邊離它最近的比它大的是該元素出棧後的棧頂元素,若是出棧後棧空,那麼該元素左邊沒有比它大的數
  • 該元素右邊離它最近的比它大的是進棧元素

而後再嘗試將進棧元素進棧,若是進棧後還會違反約定那就重複操做「彈出棧頂元素並記錄該元素信息」,直到符合約定或棧中元素所有彈出時再將該進棧元素進棧。當數組全部元素都進棧以後,棧勢必不爲空,彈出棧頂元素並記錄信息:github

  • 該元素右邊沒有比它大的數
  • 該元素左邊離它最近的比它大的數是該元素從棧彈出後的棧頂元素,若是該元素彈出後棧爲空,那麼該元素左邊沒有比它大的數

因爲每一個元素僅進棧一次、出棧一次,且出棧時能獲得題目所求信息,所以時間複雜度爲O(N)面試

示例代碼:數組

public static void findLeftAndRightBigger(int arr[]){
    Stack<Integer> stack = new Stack<>();
    for (int i = 0; i < arr.length; i++) {
        //check the agreement before push the index of element
        while (!stack.empty() && arr[stack.peek()] < arr[i]) {
            //pop and record the info(print or save)
            int index = stack.pop();
            System.out.print("index:" + index + ",element:" + arr[index] + ",right bigger is:" + arr[i]);
            if (stack.empty()) {
                System.out.print(",hasn't left bigger\n");
            } else {
                System.out.println(",left bigger is:" + arr[stack.peek()]+"\n");
            }
        }
        //push
        stack.push(i);
    }
    while (!stack.empty()) {
        int index = stack.pop();
        System.out.print("index:" + index + ",element:" + arr[index] + ",hasn't right bigger");
        if (stack.empty()) {
            System.out.print(",hasn't left bigger\n");
        } else {
            System.out.println(",left bigger is:" + arr[stack.peek()]+"\n");
        }
    }
}

public static void main(String[] args) {
    int[] arr = {2, 1, 7, 4, 5, 9, 3};
    findLeftAndRightBigger(arr);
}
複製代碼

給你一些數,建立一棵大根堆二叉樹

思路:使用一個棧底到棧頂單調遞減的單調棧,將這些數arr[]依次入棧,記錄每一個數左邊離它最近的比它大的數,保存在left[]中(下標和arr[]一一對應),記錄每一個數右邊離它最近的比它大的數,保存在right[]中。緩存

遍歷arr[]建樹:left[i]right[i]都不存在的,說明arr[i]是最大的數,將其做爲根節點;對於其餘任何一個數arr[i]left[i]right[i]必有一個存在,若是都存在則將arr[i]做爲Math.min(left[i],right[i])的孩子節點,若是隻有一個存在(如left[i])那就將arr[i]做爲left[i]的孩子節點bash

思考:這樣建出的樹會不會是森林,會不會不是二叉樹?app

找出矩陣中一片1相連的最大矩形

矩陣中的數只會是0或1,求矩陣中一片1造成的最大長方形區域的面積。dom

此題可借鑑在直方圖中找最大矩形的方法。首先一個數組能夠對應一個直方圖,以下所示:

接着,遍歷數組,以當前遍歷元素值爲杆子的高並嘗試向左右移動這根杆子(約定杆子不能出黃色區域):

如上圖,0號杆子向左右移動一格都會使杆子出界(黃色區域),所以0號杆子的活動面積是4x1=4(杆長x能活動的格子數);1號杆子向左、向右都只能移動一格,所以其活動面積是2x3=6;2號杆子的活動面積是3x1=3;3號杆子的活動面積是1x5=5;4號杆子的活動面積是6x1=6。所以該直方圖中最大矩形面積就是全部杆子的活動面積中最大的那個,即6。

若是如今給你一個矩陣,好比

0 0 0 0 1
0 0 0 0 1 
1 0 0 0 1
1 0 1 0 1
1 1 1 0 1
1 1 1 1 1
複製代碼

你可否將其中相連的一片1當作直方圖中的黃色區域,如此的話求矩陣由一片1造成的最大矩形區域就是求直方圖中最大矩形面積了。

因此對於輸入的矩形,咱們只要遍歷每一行,以該行做爲直方圖的x軸,求出直方圖的最大矩形面積,再比較全部行對應的最大矩形面積就能得出整個矩陣的一片1造成的最大矩形區域了。

以上面的矩陣爲例,第一行、第三行、最後一行對應的直方圖以下所示:

分別能夠用數組[0,0,0,0,1][1,0,0,0,3][4,2,3,1,6]來表示,那麼此題關鍵的點就是遍歷每一行並求出以該行爲x軸的直方圖的數組表示以後,如何得出此直方圖的最大矩形面積。下面就使用單調棧來解決此問題:

[4,2,3,1,6]的求解過程爲例,使用一個棧底到棧頂單調遞增的棧將數組中的數的下標做爲該數的表明依次壓棧(數的下標->數值),首先能壓的是0->4,接着準備壓1->2,發現2比棧頂的4小,壓人後會違反棧底到棧頂單調遞增的約定,所以彈出0->4並記錄0號杆子的活動面積(0->4彈出後棧爲空,說明0號杆子左移到x軸的-1就跑出黃色區域了,因爲是1->2讓它彈出的,因此0號杆子右移到x軸的1就出界了,所以0號杆子只能在x軸上的0位置上活動,活動面積是4x1=4,稱這個記錄的過程爲結算)。因爲彈出0->4以後棧空了,因此能夠壓入1->22->3,接着準備壓3->1時發現1比棧頂3小,所以結算2->3(因爲彈出2->3以後棧頂爲1->2,所以2號杆子左移到x軸1位置時出界了,因爲是3->1讓其彈出的,因此2號杆子右移到x軸3位置就出界了,所以2號杆子的活動面積是3x1=3)。接着再準備壓3->1,發現1比棧頂1->22小,所以結算1->2(彈出1->2後棧空,所以1號杆子左移到x軸-1時纔出界,3->1讓其出界的,所以右移到3時纔出界,活動面積爲2x3=6)……

全部數壓完以後,棧確定不爲空,那麼棧中剩下的還須要結算,所以依次彈出棧頂進行結算,好比[4,2,3,1,6]壓完以後,棧中還剩3->1,4->6,所以彈出4->6並結算(因爲4->6不是由於一個比6小的數要進來而讓它彈出的,因此4號杆子右移到x軸arr.length=5位置纔出界,因爲彈出後棧不空且棧頂爲3->1,因此左移到x軸的3位置上纔出界的,因此活動面積爲6x1=6;一樣的方法結算3->1……直到棧中的都被結算完,整個過程結束。

示例代碼:

public static int maxRectangleArea(int matrix[][]){
    int arr[] = new int[matrix[0].length];
    int maxArea = Integer.MIN_VALUE;
    for (int i = 0; i < matrix.length; i++) {
        for (int j = 0; j < matrix[i].length; j++) {
            arr[j] = matrix[i][j] == 1 ? arr[j]+1 : 0;
        }
        System.out.println(Arrays.toString(arr));
        maxArea = Math.max(maxArea, maxRecAreaOfThRow(arr));
    }
    return maxArea;
}

public static int maxRecAreaOfThRow(int arr[]){
    int maxArea = Integer.MIN_VALUE;
    Stack<Integer> stack = new Stack<>();
    for (int i = 0; i < arr.length; i++) {
        while (!stack.empty() && arr[i] < arr[stack.peek()]) {
            int index = stack.pop();
            int leftBorder = stack.empty() ? -1 : stack.peek();
            maxArea = Math.max(maxArea, arr[index] * (i - leftBorder - 1));
        }
        stack.push(i);
    }
    while (!stack.empty()) {
        int index = stack.pop();
        int rightBorder = arr.length;
        int leftBorder = stack.empty() ? -1 : stack.peek();
        maxArea = Math.max(maxArea, arr[index] * (rightBorder - leftBorder - 1));
    }
    return maxArea;
}

public static void main(String[] args) {
    int matrix[][] = {
        {0, 0, 0, 0, 1},
        {0, 0, 0, 0, 1},
        {1, 0, 0, 0, 1},
        {1, 0, 1, 0, 1},
        {1, 1, 1, 0, 1},
        {1, 1, 1, 1, 1}
    };
    System.out.println(maxRectangleArea(matrix));//6
}
複製代碼

烽火相望

【網易原題】給你一個數組,數組中的每一個數表明一座山的高度,這個數組表明將數組中的數從頭至尾鏈接而成的環形山脈。好比數組[2,1,3,4,5]造成的環形山脈以下:

其中藍色的圓圈就表明一座山,圈中的數字表明這座山的高度。如今在每座山的山頂都點燃烽火,假設你處在其中的一個山峯上,要想看到另外一座山峯的烽火需知足如下兩個條件中的一個:

  • 你想看的山峯在環形路徑上與你所在的山峯相鄰。好比你在山峯A上,那麼你可以看到B和E上的烽火。
  • 若是你想看的山峯和你所在的山峯不相鄰,那麼你能夠沿環形路徑順時針看這座山也能夠沿環形路徑逆時針看這座山,只要你放眼望去沿途通過的山峯高度小於你所在的山峯和目標山峯,那麼也能看到。好比C想看E,那麼能夠經過C->B->A->E的方式看,也能夠經過C->D->E的方式看。前者因爲通過的山峯的高度1和2比C的高度3和E的高度5都小,所以能看到;但後者通過的山峯D的高度4大於C的高度3,所以C在經過C->D->E這個方向看E的時候視線就被山峯D給擋住了。

問:全部山峯中,能互相看到烽火的兩兩山峯的對數。以[2,1,3,4,5]爲例,能互相看見的有:2,1,1,3,3,4,4,5,5,2,2,3,3,5,共7對。

此題分一下兩種狀況

一、數組中無重複的數

這種狀況下,答案能夠直接經過公式2*N-3能夠求得(其中N爲數組長度),證實以下:

假設A是在山峯中最高,B在全部山峯中第二高。那麼環形路徑上介於A和B之間的任意一座山峯(好比K),逆時針方向在到達A以前或到達A時必定會遇到第一座比它高的山峯,記這座山峯和K是一對;順時針方向,在到達B以前或到達B時,必定會遇到第一個比K高的山峯,記這座山峯和K是一對。也就是說對於除A,B以外的全部山峯,都能找到兩對符合標準的,這算下來就是(N-2)*2了,最後AB也算一對,總數是(N-2)*2+1=2N-3

但若是數組中有重複的數就不能採用上述的方法了

二、數組中可能有重複的數

利用單調棧

首先找出數組中最大數第一次出現的位置,記爲M。從這個數開始遍歷數組並依次壓棧(棧底到棧底從大到小的單調棧),以以下的環形山脈爲例:

從M開始壓棧,同時附帶一個計數器:

當壓入5時,違反單調棧約定所以結算4(4左邊第一個比它高的是9,右邊第一個比它高的是5,所以能和4配對的有兩對);接着再壓入五、壓入4,重點來了:連續兩次再壓入4該如何處理:

這是數組中有重複的數時,如何使用單調棧解決此題的關鍵:若是壓入的元素與棧頂元素相同,將棧頂元素的計數器加1,那麼再壓入兩個4以後棧中狀況:

而後壓入9,致使彈出並結算4。那麼如何結算計數器大於1的數據呢?首先,這3座高度相同的山峯兩兩配對可以組成C(3,2)=3對,此外其中的每座山峯左邊離它最近的比它高的是五、右邊離它近的比它大的是9,所以這3座山峯每座都能和五、9配對,即3*2=6,所以結算結果爲3+6=9……

若是數據壓完了,那就從棧頂彈出數據進行結算,直到結算棧底上一個元素以前(棧底元素是最大值),彈出數據的結算邏輯都是C(K,2)+K*2(其中K是該數據的計數器數值)。

倒數第二條數據的結算邏輯有點複雜,如圖,以結算4爲例:

若是K的數值大於1,那麼這6座高度爲4的山峯結算邏輯仍是上述公式。但若是K爲1,那麼結算公式就是C(K,2)+K*1了。

最後對於最大值M的結算,假設其計數器的值爲K,若是K=1,那麼結算結果爲0;若是K>1,那麼結算結果爲C(K,2)

示例代碼:

public static class Record{
    int value;
    int times;
    public Record(int value) {
        this.value = value;
        this.times = 1;
    }
}

public static int comunications(int[] arr) {
    //index of first max value
    int maxIndex = 0;
    for (int i = 0; i < arr.length; i++) {
        maxIndex = arr[maxIndex] < arr[i] ? i : maxIndex;
    }

    Stack<Record> stack = new Stack<>();
    stack.push(new Record(arr[maxIndex]));

    int res = 0;
    int index = nextIndex(arr, maxIndex);
    while (index != maxIndex) {
        while (!stack.empty() && arr[index] > stack.peek().value) {
            Record record = stack.pop();
            res += getInternalPairs(record.times) + record.times * 2;
        }
        if (arr[index] == stack.peek().value) {
            stack.peek().times++;
        } else {
            stack.push(new Record(arr[index]));
        }
        index = nextIndex(arr, index);
    }

    while (!stack.empty()) {
        Record record = stack.pop();
        res += getInternalPairs(record.times);
        if (!stack.empty()) {
            res += record.times;
            if (stack.size() > 1) {
                res += record.times;
            } else {
                res += stack.peek().times > 1 ? record.times : 0;
            }
        }
    }
    return res;
}

//C(K,2)
public static int getInternalPairs(int times){
    return (times * (times - 1)) / 2;
}

public static int nextIndex(int[] arr, int index) {
    return index < arr.length - 1 ? index + 1 : 0;
}

public static void main(String[] args) {
    int[] arr = {9, 4, 5, 4, 4, 4, 9,1};
    System.out.println(comunications(arr));
}
複製代碼

搜索二叉樹

搜索二叉樹的定義:對於一棵二叉樹中的任意子樹,其左子樹上的全部數值小於頭結點的數值,其右子樹上全部的數值大於頭結點的數值,而且樹中不存在數值相同的結點。也稱二叉查找樹。

平衡二叉樹/AVL樹

平衡性

經典的平衡二叉樹結構:在知足搜索二叉樹的前提條件下,對於一棵二叉樹中的任意子樹,其左子樹和其右子樹的高度相差不超過1。

典型搜索二叉樹——AVL樹、紅黑樹、SBT樹的原理

AVL樹

AVL樹是一種具備嚴苛平衡性的搜索二叉樹。什麼叫作嚴苛平衡性呢?那就是全部子樹的左子樹和右子樹的高度相差不超過1。弊端是,每次發現由於插入、刪除操做破壞了這種嚴苛的平衡性以後,都須要做出相應的調整以使其恢復平衡,調整較爲頻繁。

紅黑樹

紅黑樹是每一個節點都帶有顏色屬性的搜索二叉樹,顏色或紅色或黑色。在搜索二叉樹強制通常要求之外,對於任何有效的紅黑樹咱們增長了以下的額外要求:

  • 性質1. 節點是紅色或黑色。
  • 性質2. 根節點是黑色。
  • 性質3 每一個葉節點(NIL節點,空節點)是黑色的。
  • 性質4 每一個紅色節點的兩個子節點都是黑色。(從每一個葉子到根的全部路徑上不能有兩個連續的紅色節點)
  • 性質5. 從任一節點到其每一個葉子的全部路徑都包含相同數目的黑色節點。

這些約束強制了紅黑樹的關鍵性質: 從根到葉子的最長的可能路徑很少於最短的可能路徑的兩倍長。結果是這個樹大體上是平衡的。由於操做好比插入、刪除和查找某個值的最壞狀況時間都要求與樹的高度成比例,這個在高度上的理論上限容許紅黑樹在最壞狀況下都是高效的,而不一樣於普通的二叉查找樹。

要知道爲何這些特性確保了這個結果,注意到性質4致使了路徑不能有兩個毗連的紅色節點就足夠了。最短的可能路徑都是黑色節點,最長的可能路徑有交替的紅色和黑色節點。由於根據性質5全部最長的路徑都有相同數目的黑色節點,這就代表了沒有路徑能多於任何其餘路徑的兩倍長。

SBT樹

它是由中國廣東中山記念中學的陳啓峯發明的。陳啓峯於2006年末完成論文《Size Balanced Tree》,並在2007年的全國青少年信息學奧林匹克競賽冬令營中發表。相比紅黑樹、AVL樹等自平衡二叉查找樹,SBT更易於實現據陳啓峯在論文中稱,SBT是「目前爲止速度最快的高級二叉搜索樹」SBT能在O(log n)的時間內完成全部二叉搜索樹(BST)的相關操做,而與普通二叉搜索樹相比,SBT僅僅加入了簡潔的核心操做Maintain。因爲SBT賴以保持平衡的是size域而不是其餘「無用」的域,它能夠很方便地實現動態順序統計中的select和rank操做。

SBT樹的性質是:對於數中任意結點,以該結點爲根節點的子樹的結點個數不能比以該結點的叔叔結點爲根節點的子樹的結點個數大。

因爲紅黑樹的實現較爲複雜,所以如今工程中大多使用SBT樹做爲平衡二叉樹的實現。

旋轉——Rebalance

左旋:

左旋

右旋:

右旋

每種平衡二叉樹都有本身的一套在插入、刪除等操做改變樹結構而破壞既定平衡性時的應對措施(但都是左旋操做和右旋操做的組合),以AVL數爲例(有四種平衡調整操做,其中的數字只是結點代號而非結點數值):

  • LL調整:2號結點的左孩子的左孩子致使整個樹不平衡,2號結點右旋一次

  • RR調整:3號結點的右孩子的右孩子致使樹不平衡,3號結點左旋一次:

  • LR調整:先左後右

  • RL調整:先右後左:

紅黑樹的調整也是相似的,只不過調整方案更多。面試中通常不會讓你手寫紅黑樹(如有興趣可參見文末附錄),但咱們必定能說清這些查找二叉樹的性質,以及調整平衡的基本操做,再就是這些結構的使用。

Java中紅黑樹的使用

Java中紅黑樹的實現有TreeSetTreeMap,前者結點存儲的是單一數據,然後者存儲的是<key,value>的形式。

public static void main(String[] args) {
    TreeMap<Integer,String> treeMap = new TreeMap();
    treeMap.put(5, "tom");
    treeMap.put(11, "jack");
    treeMap.put(30,"tony");
    treeMap.put(18, "alice");
    treeMap.put(25, "jerry");

    //紅黑樹中最右邊的結點
    System.out.println(treeMap.lastEntry());
    System.out.println(treeMap.lastKey());
    //紅黑樹最左邊的結點
    System.out.println(treeMap.firstKey());
    //若是有13這個key,那麼返回這條記錄,不然返回樹中比13大的key中最小的那一個
    System.out.println(treeMap.ceilingEntry(13));
    //若是有21這個key,那麼返回這條記錄,不然返回樹中比21小的key中最大的那一個
    System.out.println(treeMap.floorEntry(21));
    //比11大的key中,最小的那一個
    System.out.println(treeMap.higherKey(11));
    //比25小的key中,最大的那一個
    System.out.println(treeMap.lowerKey(25));
    //遍歷紅黑樹,是按key有序遍歷的
    for (Map.Entry<Integer, String> record : treeMap.entrySet()) {
        System.out.println("age:"+record.getKey()+",name:"+record.getValue());
    }
}
複製代碼

TreeMap的優點是key在其中是有序組織的,所以增長、刪除、查找key的時間複雜度均爲log(2,N)

案例

The Skyline Problem

水平面上有 N 座大樓,每座大樓都是矩陣的形狀,能夠用一個三元組表示 (start, end, height),分別表明其在x軸上的起點,終點和高度。大樓之間從遠處看可能會重疊,求出 N 座大樓的外輪廓線。

外輪廓線的表示方法爲若干三元組,每一個三元組包含三個數字 (start, end, height),表明這段輪廓的起始位置,終止位置和高度。

給出三座大樓:

[
  [1, 3, 3],
  [2, 4, 4],
  [5, 6, 1]
]
複製代碼

外輪廓線爲:

[
  [1, 2, 3],
  [2, 4, 4],
  [5, 6, 1]
]
複製代碼

解析

  1. 將一座樓的表示[start,end,height]拆分紅左右兩個邊界(邊界包含:所處下標、邊界高度、是樓的左邊界仍是右邊界),好比[1,3,3]就能夠拆分紅[1,3,true][3,3,false]的形式(true表明左邊界、false表明右邊界)。

  2. 將每座樓都拆分紅兩個邊界,而後對邊界按照邊界所處的下標進行排序。好比[[1,3,3],[2,4,4],[5,6,1]拆分以後爲[[1,3,true],[3,3,false],[2,4,true],[,4,4,false],[5,1,true],[6,1,false]],排序後爲[[1,3,true],[2,4,true],[3,3,false],[4,4,false],[5,1,true],[6,1,false]]

  3. 將邊界排序後,遍歷每一個邊界的高度並依次加入到一棵TreeMap紅黑樹中(記爲countOfH),以該高度出現的次數做爲鍵值(第一次添加的高度鍵值爲1),若是遍歷過程當中有重複的邊界高度添加,要判斷它是左邊界仍是右邊界,前者直接將該高度在紅黑樹中的鍵值加1,後者則減1。以步驟2中排序後的邊界數組爲例,首先判斷countOfH是否添加過邊界[1,3,true]的高度3,發現沒有,因而put(3,1);接着對[2,4,true]put[4,1];而後嘗試添加[3,3,false]3,發現countOfH中添加過3,而[3,3,false]是右邊界,所以將countOfH.get(3)的次數減1,當countOfH中的記錄的鍵值爲0時直接移除,因而移除高度爲3的這一條記錄;……

    對於遍歷過程通過的每個邊界,咱們還須要一棵TreeMap紅黑樹(記爲maxHOfPos)來記錄對咱們後續求外輪廓線有用的信息,也就是每一個邊界所處下標的最大建築高度:

    這裏有個細節要注意一下,那就是若是添加某個邊界以後,countOfH樹爲空了,那麼該邊界所處下標的建築高度要記爲0,表示一片相鄰建築的結束,好比上圖中下標爲4和6的邊界。這也是爲了後續求外輪廓線提供判斷的依據。

  4. 遍歷maxHOfPos中的記錄,構造整個外輪廓線數組:

    起初沒有遍歷邊界時,記start=0,height=0,接着遍歷邊界,若是邊界高度curHeight!=height如上圖中的1->2:height=0,curHeight=3,那麼記start=1,height=3表示第一條組外輪廓線的startheight,接下來就是肯定它的end了。肯定了一條輪廓線的startheight以後會有兩種狀況:下一組輪廓線和這一組是挨着的(如上圖2->3)、下一組輪廓線和這一組是相隔的(如上圖中3->4)。所以在遍歷到邊界[index:2,H:4]時,發現curHeight=4 != height=3,因而能夠肯定輪廓線start:1,heigth:3end:2。肯定一條輪廓線後就要更新一下start=2,heigth=4表示下一組輪廓線的起始下標和高度,接着遍歷到邊界[index:3,H:4],發現curHeight=4=height因而跳過;接着遍歷到邊界[index:4,H:0],發現curHeight=0,根據步驟3中的邏輯可知一片相鄰的建築到此結束了,所以輪廓線start:2,height:4end=4

示例代碼:

package top.zhenganwen.lintcode;

import java.util.*;

public class T131_The_SkylineProblem {

    public class Border implements Comparable<Border> {
        public int index;
        public int height;
        public boolean isLeft;

        public Border(int index, int height, boolean isLeft) {
            this.index = index;
            this.height = height;
            this.isLeft = isLeft;
        }

        @Override
        public int compareTo(Border border) {
            if (this.index != border.index) {
                return this.index - border.index;
            }
            if (this.isLeft != border.isLeft) {
                return this.isLeft ? -1 : 1;
            }
            return 0;
        }
    }

    /** * @param buildings: A list of lists of integers * @return: Find the outline of those buildings */
    public List<List<Integer>> buildingOutline(int[][] buildings) {
        //一、split one building to two borders and sort by border's index
        Border[] borders = new Border[buildings.length * 2];
        for (int i = 0; i < buildings.length; i++) {
            int[] oneBuilding = buildings[i];
            borders[i * 2] = new Border(oneBuilding[0], oneBuilding[2], true);
            borders[i * 2 + 1] = new Border(oneBuilding[1], oneBuilding[2], false);
        }
        Arrays.sort(borders);

        //二、traversal borders and record the max height of each index

        //key->height value->the count of the height
        TreeMap<Integer, Integer> countOfH = new TreeMap<>();
        //key->index value->the max height of the index
        TreeMap<Integer, Integer> maxHOfPos = new TreeMap<>();
        for (int i = 0; i < borders.length; i++) {
            int height = borders[i].height;
            if (!countOfH.containsKey(height)) {
                countOfH.put(height, 1);
            }else {
                int count = countOfH.get(height);
                if (borders[i].isLeft) {
                    countOfH.put(height, count + 1);
                } else {
                    countOfH.put(height, count - 1);
                    if (countOfH.get(height) == 0) {
                        countOfH.remove(height);
                    }
                }
            }

            if (countOfH.isEmpty()) {
                maxHOfPos.put(borders[i].index, 0);
            } else {
                //lastKey() return the maxHeight in countOfH RedBlackTree->log(2,N)
                maxHOfPos.put(borders[i].index, countOfH.lastKey());
            }
        }

        //三、draw the buildings outline according to the maxHOfPos
        int start = 0;
        int height = 0;
        List<List<Integer>> res = new ArrayList<>();
        for (Map.Entry<Integer, Integer> entry : maxHOfPos.entrySet()) {
            int curPosition = entry.getKey();
            int curMaxHeight = entry.getValue();
            if (height != curMaxHeight) {
                //if the height don't be reset to 0,the curPosition is the end
                if (height != 0) {
                    List<Integer> record = new ArrayList<>();
                    record.add(start);
                    record.add(curPosition);//end
                    record.add(height);

                    res.add(record);
                }
                //reset the height and start
                height = curMaxHeight;
                start = curPosition;
            }
        }
        return res;
    }

    public static void main(String[] args) {
        int[][] buildings = {
                {1, 3, 3},
                {2, 4, 4},
                {5, 6, 1}
        };
        System.out.println(new T131_The_SkylineProblem().buildingOutline(buildings));

    }
}
複製代碼

跳錶

跳錶有着和紅黑樹、SBT樹相同的功能,都能實如今O(log(2,N))內實現對數據的增刪改查操做。但跳錶不是以二叉樹爲原型的,其設計細節以下:

記該結構爲SkipList,該結構中能夠包含有不少結點(SkipListNode),每一個結點表明一個被添加到該結構的數據項。當實例化SkipList時,該對象就會自帶一個SkipListNode(不表明任何數據項的頭結點)。

添加數據

當你向其中添加數據以前,首先會拋硬幣,將第一次出現正面朝上時硬幣被拋出的次數做爲該數據的層數(level最小爲1),接着將數據和其層數封裝成一個SkipListNode添加到SkipList中。結構初始化時,其頭結點的層數爲0,但每次添加數據後都會更新頭結點的層數爲所添數據中層數最大的。好比實例化一個SkipList後向其中添加一條層數爲3的數據7

這時若是再添加一條層數爲2的數據5呢?首先遊標curNode會從head的最高層出發往右走,走到數據項爲7的結點,發現7>5,因而又退回來走向下一層:

接着再嘗試往右走,仍是發現7>5,因而仍是準備走向下一層,但此時發現curNode所在層數2是數據項5的最高層,因而先建出數據項5的第二層,curNode再走向下一層:

一樣的,curNode嘗試往右走,但發現7>5curNode所在層爲1,但數據5的第一層還沒建,因而建出,curNode再往下走。當curNode走到null時,建出數據5根部的null

至此層數爲2的數據項5的添加操做完畢。

那若是添加一個層數較高的數據項該如何處理呢?以添加層數爲4的數據10爲例:

添加操做對應的代碼示例:

import java.util.ArrayList;

/** * A stored structure.Its add,delete,update,find operation are log(2,N) * * @author zhenganwen */
public class SkipList {
    private SkipListNode head;
    private int maxLevel;
    private int size;
    public static final double PROBABILITY = 0.5;

    public SkipList() {
        this.head = new SkipListNode(Integer.MIN_VALUE);
        /** * the 0th level of each SkipListNode is null */
        this.head.nextNodes.add(null);
        this.maxLevel = 0;
        this.size = 0;
    }

    private class SkipListNode {
        int value;
        /** * nextNodes represent the all levels of a SkipListNode the element on * one index represent the successor SkipListNode on the indexth level */
        ArrayList<SkipListNode> nextNodes;

        public SkipListNode(int newValue) {
            this.value = newValue;
            this.nextNodes = new ArrayList<SkipListNode>();
        }
    }

    /** * put a new data into the structure->log(2,N) * * @param newValue */
    public void add(int newValue) {
        if (!contains(newValue)) {

            // generate the level
            int level = 1;
            while (Math.random() < PROBABILITY) {
                level++;
            }
            // update max level
            if (level > maxLevel) {
                int increment = level - maxLevel;
                while (increment-- > 0) {
                    this.head.nextNodes.add(null);
                }
                maxLevel = level;
            }
            // encapsulate value
            SkipListNode newNode = new SkipListNode(newValue);
            // build all the levels of new node
            SkipListNode cur = findInsertionOfTopLevel(newValue, level);
            while (level > 0) {
                if (cur.nextNodes.get(level) != null) {
                    newNode.nextNodes.add(0, cur.nextNodes.get(level));
                } else {
                    newNode.nextNodes.add(0, null);
                }
                cur.nextNodes.set(level, newNode);
                level--;
                cur = findNextInsertion(cur, newValue, level);
            }
            newNode.nextNodes.add(0, null);
            size++;
        }
    }

    /** * find the insertion point of the newNode's top level from head's maxLevel * by going right or down * * @param newValue newNode's value * @param level newNode's top level * @return */
    private SkipListNode findInsertionOfTopLevel(int newValue, int level) {
        int curLevel = this.maxLevel;
        SkipListNode cur = head;
        while (curLevel >= level) {
            if (cur.nextNodes.get(curLevel) != null
                    && cur.nextNodes.get(curLevel).value < newValue) {
                // go right
                cur = cur.nextNodes.get(curLevel);
            } else {
                // go down
                curLevel--;
            }
        }
        return cur;
    }

    /** * find the next insertion from cur node by going right on the level * * @param cur * @param newValue * @param level * @return */
    private SkipListNode findNextInsertion(SkipListNode cur, int newValue, int level) {
        while (cur.nextNodes.get(level) != null
                && cur.nextNodes.get(level).value < newValue) {
            cur = cur.nextNodes.get(level);
        }
        return cur;
    }

    /** * check whether a value exists->log(2,N) * * @param value * @return */
    public boolean contains(int value) {
        if (this.size == 0) {
            return false;
        }
        SkipListNode cur = head;
        int curLevel = maxLevel;
        while (curLevel > 0) {
            if (cur.nextNodes.get(curLevel) != null) {
                if (cur.nextNodes.get(curLevel).value == value) {
                    return true;
                } else if (cur.nextNodes.get(curLevel).value < value) {
                    cur = cur.nextNodes.get(curLevel);
                } else {
                    curLevel--;
                }
            } else {
                curLevel--;
            }
        }

        return false;
    }

    public static void main(String[] args) {
        SkipList skipList = new SkipList();
        skipList.add(1);
        skipList.add(2);
        skipList.add(3);
        skipList.add(4);
        skipList.add(5);
        //mark a break point here to check the memory structure of skipList
        System.out.println(skipList);
    }

}
複製代碼

查找數據

查找數據項的操做和添加數據項的步驟相似,也是遊標curNodehead的最高層出發,每次先嚐試向右走來到nextNode,若是nextNode封裝的數據大於查找的目標targetnextNode爲空,那麼curNode回退並向下走;若是nextNode封裝的數據小於target,那麼curNode繼續向右走,直到curNode走到的結點數據與target相同表示找到了,不然curNode走到了某一結點的根部null,那麼說明結構中不存在該數據。->contains()

刪除數據

瞭解添加數據的過程以後,刪除數據其實就是將邏輯倒過來:解除該數據結點的先後引用關係。下圖是我在寫好上述add()方法後,向其中放入一、二、三、四、5後造成的結構:

若是此時刪除數據3

首先應該從head的最高層出發,經過向右或向下找到數據3的最高層(如圖2->3->5->6->7),將該層移除總體結構並處理好該層上,其先後結點的關係。一樣的邏輯,將數據3剩下的層移除。

示例代碼:

/** * delete skipListNode by the value * * @param value */
public void delete(int value) {
    //if exists
    if (contains(value)) {
        //find the node and its level
        SkipListNode deletedNode = head;
        int deletedLevels = maxLevel;
        //because exists,so must can find
        while (deletedLevels > 0) {
            if (deletedNode.nextNodes.get(deletedLevels) != null) {
                if (deletedNode.nextNodes.get(deletedLevels).value == value) {
                    deletedNode = deletedNode.nextNodes.get(deletedLevels);
                    break;
                } else if (deletedNode.nextNodes.get(deletedLevels).value < value) {
                    deletedNode = deletedNode.nextNodes.get(deletedLevels);
                } else {
                    deletedLevels--;
                }
            } else {
                deletedLevels--;
            }
        }
        //release the node and adjust the reference
        while (deletedLevels > 0) {
            SkipListNode pre = findInsertionOfTopLevel(value, deletedLevels);
            if (deletedNode.nextNodes.get(deletedLevels) != null) {
                pre.nextNodes.set(deletedLevels, deletedNode.nextNodes.get(deletedLevels));
            } else {
                pre.nextNodes.set(deletedLevels, null);
            }
            deletedLevels--;
        }

        size--;
    }
}

public static void main(String[] args) {
    SkipList skipList = new SkipList();
    skipList.add(1);
    skipList.add(2);
    skipList.add(3);
    skipList.add(4);
    skipList.add(5);
    //mark a break point here to check the memory structure of skipList
    skipList.delete(3);
    System.out.println(skipList);
}
複製代碼

遍歷數據

須要遍歷跳錶中的數據時,咱們能夠根據每一個數據的層數至少爲1的特色(每一個結點的第一層引用的是比該結點數據大的結點中數據最小的結點)。

示例代碼:

class SkipListIterator implements Iterator<Integer> {
    private SkipListNode cur;
    public SkipListIterator(SkipList skipList) {
        this.cur = skipList.head;
    }

    @Override
    public boolean hasNext() {
        return cur.nextNodes.get(1) != null;
    }

    @Override
    public Integer next() {
        int value = cur.nextNodes.get(1).value;
        cur = cur.nextNodes.get(1);
        return value;
    }
}

@Override
public String toString() {
    SkipListIterator iterator = new SkipListIterator(this);
    String res = "[ ";
    while (iterator.hasNext()) {
        res += iterator.next()+" ";
    }
    res += "]";
    System.out.println();
    return res;
}

public static void main(String[] args) {
    SkipList skipList = new SkipList();
    skipList.add(1);
    skipList.add(2);
    skipList.add(3);
    skipList.add(4);
    skipList.add(5);
    System.out.println(skipList);
    skipList.delete(3);
    System.out.println(skipList);
}
複製代碼

從暴力嘗試到動態規劃

動態規劃不是玄學,也無需去記那些所謂的刻板的「公式」(例如狀態轉換表達式等),其實動態規劃是從暴力遞歸而來。並非說一個能夠動態規劃的題一上來就能夠寫出動態規劃的求解步驟,咱們只須要可以寫出暴力遞歸版本,而後對重複計算的子過程結果作一個緩存,最後分析狀態依賴尋求最優解,即衍生成了動態規劃。本節將以多個例題示例,展現求解過程是如何從暴力嘗試,一步步到動態規劃的。

換錢的方法數

題目:給定數組arr,arr中全部的值都爲正數且不重複。每一個值表明一種面值的貨幣,每種面值的貨幣可使用任意張,再給定一個整數aim表明要找的錢數,求換錢有多少種方法。

舉例:arr=[5,10,25,1],aim=0:成0元的方法有1種,就是全部面值的貨幣都不用。因此返回1。arr=[5,10,25,1],aim=15:組成15元的方法有6種,分別爲3張5元、1張10元+1張5元、1張10元+5張1元、10張1元+1張5元、2張5元+5張1元和15張1元。因此返回6。arr=[3,5],aim=2:任何方法都沒法組成2元。因此返回0。

暴力嘗試

咱們能夠將該題要求解的問題定義成一個過程:對於下標indexarr中在index及其以後的全部面值不限張數任意組合,該過程最終返回全部有效的組合方案。所以該過程能夠描述爲int process(int arr[],int index,int aim),題目的解就是調用process(arr,0,aim)。那麼函數內部具體該如何解決此問題呢?

其實全部面值不限張數的任意組合就是對每個面值須要多少張的一個決策,那咱們不妨從碰到的第一個面值開始決策,好比 arr=[5,10,25,1],aim=15時,( 選0張5元以後剩下的面值不限張數組合成15元的方法數 + 選1張5元以後剩下的面值不限張數組合成10元方法數 + 選2張5元以後剩下的面值不限張數組合成5元方法數 + 選3張5元以後剩下的面值不限張數組合成0元方法數 )就是所給參數對應的解,其中「剩下的面值不限張數組合成必定的錢數」又是同類問題,可使用相同的過程求解,所以有了以下的暴力遞歸:

/** * arr中的每一個元素表明一個貨幣面值,使用數組index及其以後的面值(不限張數) * 拼湊成錢數爲aim的方法有多少種,返回種數 * @param arr * @param index * @param aim * @return */
public static int process(int arr[], int index, int aim) {
    if (index == arr.length) {
        return aim == 0 ? 1 : 0;
    }
    int res = 0;
    //index位置面值的決策,從0張開始
    for (int zhangshu = 0; arr[index] * zhangshu <= aim; zhangshu++) {
        res += process(arr, index + 1, aim - (arr[index] * zhangshu));
    }
    return res;
}

public static int swapMoneyMethods(int arr[], int aim) {
    if (arr == null) {
        return 0;
    }
    return process(arr, 0, aim);
}

public static void main(String[] args) {
    int arr[] = {5, 10, 25, 1};
    System.out.println(swapMoneyMethods(arr, 15));
}
複製代碼

緩存每一個狀態的結果,以避免重複計算

上述的暴力遞歸是極其暴力的,好比對於參數 arr=[5,3,1,30,15,20,10],aim=100來講,若是已經決策了3張5元+0張3元+0張1元的接着會調子過程process(arr, 3, 85);若是已經決策了0張5元+5張3元+0張1元接着也會調子過程process(arr, 3, 85);若是已經決策了0張5元+0張3元+15張1元接着仍是會調子過程process(arr, 3, 85)

你會發現,這個已知面額種類和要湊的錢數,求湊錢的方法的解是固定的。也就是說無論以前的決策是3張5元的,仍是5張3元的,又或是15張1元的,對後續子過程的[30,15,20,10]湊成85這個問題的解是不影響的,這個解該是多少仍是多少。這也是無後效性問題。無後效性問題就是某一狀態的求解不依賴其餘狀態,好比著名的N皇后問題就是有後效性問題。

所以,咱們不妨再求解一個狀態以後,將該狀態對應的解作個緩存,在後續的狀態求解時先到緩存中找是否有該狀態的解,有則直接使用,沒有再求解並放入緩存,這樣就不會有重複計算的狀況了:

public static int swapMoneyMethods(int arr[], int aim) {
    if (arr == null) {
        return 0;
    }
    return process2(arr, 0, aim);
}

/** * 使用哈希表左緩存容器 * key是某個狀態的代號,value是該狀態對應的解 */
static HashMap<String,Integer> map = new HashMap();

public static int process2(int arr[], int index, int aim) {
    if (index == arr.length) {
        return aim == 0 ? 1 : 0;
    }
    int res = 0;
    for (int zhangshu = 0; arr[index] * zhangshu <= aim; zhangshu++) {
        //使用index及其以後的面值拼湊成aim的方法數這個狀態的代號:index_aim
        String key = String.valueOf(index) + "_" + String.valueOf(aim);
        if (map.containsKey(key)) {
            res += map.get(key);
        } else {
            int value = process(arr, index + 1, aim - (arr[index] * zhangshu));
            key = String.valueOf(index + 1) + "_" + String.valueOf(aim - (arr[index] * zhangshu));
            map.put(key, value);
            res += value;
        }
    }
    return res;
}

public static void main(String[] args) {
    int arr[] = {5, 10, 25, 1};
    System.out.println(swapMoneyMethods(arr, 15));
}
複製代碼

肯定依賴關係,尋找最優解

固然,藉助緩存已經將暴力遞歸的時間複雜度拉低了不少,但這還不是最優解。下面咱們將以尋求最優解爲引導,挖掘出動態規劃中的狀態轉換。

從暴力嘗試到動態規劃,咱們只需觀察暴力嘗試版本的代碼,甚至能夠忘卻題目,按照下面高度套路化的步驟,就能夠輕易改出動態規劃:

  1. 首先每一個狀態都有兩個參數indexaimarr做爲輸入參數是不變的),所以能夠對應兩個變量的變化範圍創建一張二維表:

  2. base case中找出特殊位置的解。好比if(index==arr.length) return aim==0?1:0,那麼上述二維表的最後一行對應的全部狀態能夠直接求解:

  3. 從暴力遞歸中找出廣泛位置對應的狀態所依賴的其餘狀態。好比:

    for (int zhangshu = 0; arr[index] * zhangshu <= aim; zhangshu++) {
        res += process(arr, index + 1, aim - (arr[index] * zhangshu));
    }
    複製代碼

    那麼對於二維表中的一個廣泛位置(i,j),它所依賴的狀態以下所示:

    也就是說一個廣泛位置的狀態依賴它的下一行的幾個位置上的狀態。那麼咱們已經知道了最後一行全部位置上的狀態,固然能夠根據這個依賴關係推出倒數第二行的,繼而推出倒數第三行的……整個二維表的全部位置上的狀態都能推出來。

  4. 找出主問題對應二維表的哪一個狀態((0,maxAim)),那個狀態的值就是問題的解。

示例代碼:

public static int maxMethodsDp(int arr[], int aim) {
    //二維表
    int dp[][] = new int[arr.length + 1][aim + 1];
    //base case
    dp[arr.length][0] = 1;
    //從倒數第二行開始推,推出整個二維表每一個位置的狀態
    for (int i = arr.length - 1; i >= 0; i--) {
        for (int j = 0; j <= aim; j++) {
            //i對應的面值取0張
            dp[i][j] = dp[i + 1][j];
            //i對應的面值取1張、2張、3張……
            for (int subAim = j - arr[i]; subAim >= 0; subAim = subAim - arr[i]) {
                dp[i][j] += dp[i + 1][subAim];
            }
        }
    }

    return dp[0][aim];
}

public static void main(String[] args) {
    int arr[] = {5, 10, 25, 1};
    System.out.println(maxMethodsDp(arr, 15));
}
複製代碼

到這裏也許你會送一口氣,終於找到了最優解,其實否則,由於若是你再分析一下每一個狀態的求解過程,仍然存在瑕疵:

好比你在求解狀態A時,可能會將其依賴的狀態M,N,P的值累加起來;而後在求解狀態B時,有須要將其依賴的狀態M,N,P,Q累加起來,你會發如今這個過程當中M+N+P的計算是重複的,所以還能夠有以下優化:

for (int i = arr.length - 1; i >= 0; i--) {
    for (int j = 0; j <= aim; j++) {
        dp[i][j] = dp[i + 1][j];
        if (j - arr[i] >= 0) {
            dp[i][j] += dp[i][j - arr[i]];
        }
    }
}
複製代碼

至此,此題最優解的求解完畢。

排成一條線的紙牌博弈問題

**題目:**給定一個整型數組arr,表明分數不一樣的紙牌排成一條線。玩家A和玩家B依次拿走每張紙牌,規定玩家A先拿,玩家B後拿,可是每一個玩家每次只能拿走最左或最右的紙牌,玩家A和玩家B都絕頂聰明。請返回最後獲勝者的分數。

舉例:arr=[1,2,100,4]。開始時玩家A只能拿走1或4。若是玩家A拿走1,則排列變爲[2,100,4],接下來玩家B能夠拿走2或4,而後繼續輪到玩家A。若是開始時玩家A拿走4,則排列變爲[1,2,100],接下來玩家B能夠拿走1或100,而後繼續輪到玩家A。玩家A做爲絕頂聰明的人不會先拿4,由於拿4以後,玩家B將拿走100。因此玩家A會先拿1,讓排列變爲[2,100,4],接下來玩家B無論怎麼選,100都會被玩家A拿走。玩家A會獲勝,分數爲101。因此返回101。arr=[1,100,2]。開始時玩家A無論拿1仍是2,玩家B做爲絕頂聰明的人,都會把100拿走。玩家B會獲勝,分數爲100。因此返回100。

動態規劃的題難就難在暴力嘗試這個「試」法,只要可以試出了暴力版本,那改成動態規劃就是高度套路的。

暴力嘗試

public static int maxScoreOfWinner(int arr[]) {
    if (arr == null) {
        return 0;
    }
    return Math.max(
        f(arr, 0, arr.length-1),
        s(arr, 0, arr.length-1));
}

public static int f(int arr[], int beginIndex, int endIndex) {
    if (beginIndex == endIndex) {
        return arr[beginIndex];
    }
    return Math.max(
        arr[beginIndex] + s(arr, beginIndex + 1, endIndex),
        arr[endIndex] + s(arr, beginIndex, endIndex - 1));
}

public static int s(int arr[], int beginIndex, int endIndex) {
    if (beginIndex == endIndex) {
        return 0;
    }
    return Math.min(
        f(arr, beginIndex + 1, endIndex),
        f(arr, beginIndex, endIndex - 1));
}

public static void main(String[] args) {
    int arr[] = {1, 2, 100, 4};
    System.out.println(maxScoreOfWinner(arr));//101
}
複製代碼

這個題的試法其實很不容易,筆者直接看別人寫出的暴力嘗試版本表示根本看不懂,最後仍是搜了博文才弄懂。其中f()s()就是整個嘗試中的思路,與以往窮舉法的暴力遞歸不一樣,這裏是兩個函數相互遞歸調用。

f(int arr[],int begin,int end)表示若是紙牌只剩下標在begin~end之間的幾個了,那麼做爲先拿者,紙牌被拿完後,先拿者能達到的最大分數;而s(int arr[],int begin,int end)表示若是紙牌只剩下標在begin~end之間的幾個了,那麼做爲後拿者,紙牌被拿完後,後拿者能達到的最大分數。

f()中,若是隻有一張紙牌,那麼該紙牌分數就是先拿者能達到的最大分數,直接返回,無需決策。不然先拿者A的第一次決策只有兩種狀況:

  • 先拿最左邊的arr[beginIndex],那麼在A拿完這一張以後就會做爲後拿者參與到剩下的(begin+1)~end之間的紙牌的決策了,這一過程能夠交給s()來作。
  • 先拿最右邊的arr[endIndex],那麼在A拿完這一張以後就會做爲後拿者參與到剩下的begin~(end-1)之間的紙牌的決策了,這一過程能夠交給s()來作。

最後返回兩種狀況中,結果較大的那種。

s()中,若是隻有一張紙牌,那麼做爲後拿者沒有紙牌可拿,分數爲0,直接返回。不然以假設的方式巧妙的將問題遞歸了下去:

  • 假設先拿者A拿到了arr[beginIndex],那麼去掉該紙牌後,對於剩下的(begin+1)~end之間的紙牌,後拿者B就轉變身份成了先拿者,這一過程能夠交給f()來處理。
  • 假設先拿者A拿到了arr[endIndex],那麼去掉該紙牌後,對於剩下的begin~(end-1)之間的紙牌,後拿者B就轉變身份成了先拿者,這一過程能夠交給f()來處理。

這裏取兩種狀況中結果較小的一種,是由於這兩種狀況是咱們假設的,但先拿者A絕頂聰明,他的選擇確定會讓後拿者儘量拿到更小的分數。好比arr=[1,2,100,4],雖然咱們的假設有先拿者拿1和拿4兩種狀況,對應f(arr,1,3)f(arr,0,2),但實際上先拿者不會讓後拿者拿到100,所以取兩種狀況中結果較小的一種。

改動態規劃

這裏是兩個函數相互遞歸,每一個函數的參數列表又都是beginIndexendIndex是可變的,所以須要兩張二維表保存(begin,end)肯定時,f()s()的狀態值。

  1. 肯定base case對應的特殊位置上的狀態值:

    能夠發現兩張表的對角線位置上的狀態值都是能夠肯定的,begin<=end,所以對角線左下方的區域不用管。

  2. 由遞歸調用邏輯找出狀態依賴。

    f()依賴的狀態:

    return Math.max(
                    arr[beginIndex] + s(arr, beginIndex + 1, endIndex),
                    arr[endIndex] + s(arr, beginIndex, endIndex - 1));
    複製代碼

    F表的(begin,end)依賴S表(begin+1,end)(begin,end-1)

    s()依賴的狀態:

    return Math.min(
                    f(arr, beginIndex + 1, endIndex),
                    f(arr, beginIndex, endIndex - 1));
    複製代碼

    S表的(begin,end)依賴F表的(begin+1,end)(begin,end-1)

    如此的話,對於對角線的右上區域,對角線位置上的狀態能推出倒數第二長對角線位置上的狀態,進而推出倒數第三長位置上的狀態……右上區域每一個位置的狀態都能推出。

  3. 肯定主問題對應的狀態:

    return Math.max(
                    f(arr, 0, arr.length-1),
                    s(arr, 0, arr.length-1));
    複製代碼

示例代碼:

public static int maxScoreOfWinnerDp(int arr[]) {
    if (arr == null || arr.length == 0) {
        return 0;
    }

    int F[][] = new int[arr.length][arr.length];
    int S[][] = new int[arr.length][arr.length];
    for (int i = 0; i < arr.length; i++) {
        for (int j = 0; j < arr.length; j++) {
            if (i == j) {
                F[i][i] = arr[i];
            }
        }
    }
    //依次推出每條對角線,一共n-1條
    for (int i = 1; i < arr.length; i++) {
        for (int row = 0; row < arr.length - i; row++) {
            int col = row + i;
            F[row][col] = Math.max(arr[row] + S[row + 1][col], arr[col] + S[row][col - 1]);
            S[row][col] = Math.min(F[row + 1][col], F[row][col - 1]);
        }
    }

    return Math.max(F[0][arr.length - 1], S[0][arr.length - 1]);
}

public static void main(String[] args) {
    int arr[] = {1, 2, 100, 4};
    System.out.println(maxScoreOfWinnerDp(arr));
}
複製代碼

代碼優化:

if (arr == null || arr.length == 0) {
    return 0;
}
int[][] f = new int[arr.length][arr.length];
int[][] s = new int[arr.length][arr.length];
for (int j = 0; j < arr.length; j++) {
    f[j][j] = arr[j];
    for (int i = j - 1; i >= 0; i--) {
        f[i][j] = Math.max(arr[i] + s[i + 1][j], arr[j] + s[i][j - 1]);
        s[i][j] = Math.min(f[i + 1][j], f[i][j - 1]);
    }
}
return Math.max(f[0][arr.length - 1], s[0][arr.length - 1]);
複製代碼

機器人走路問題

給你標號爲一、二、三、……、N的N個位置,機器人初始停在M位置上,走P步後停在K位置上的走法有多少種。注:機器人在1位置上時只能向右走,在N位置上時只能向左走,其它位置既可向右又可向左。

public static int process(int N, int M, int P, int K) {
    if (P == 0) {
        return M == K ? 1 : 0;
    }
    if (M == 1) {
        return process(N, M + 1, P - 1, K);
    } else if (M == N) {
        return process(N, M - 1, P - 1, K);
    }
    return process(N, M + 1, P - 1, K) + process(N, M - 1, P - 1, K);
}

public static void main(String[] args) {
    System.out.println(process(5, 2, 3, 3));
}
複製代碼

這裏暴力遞歸參數列表的可變變量有MP,根據base case和其它特殊狀況畫出二維表:

動態規劃示例代碼:

public static int robotWalkWaysDp(int N, int M, int P, int K) {
    int dp[][] = new int[N + 1][P + 1];
    dp[K][0] = 1;
    for (int j = 1; j <= P; j++) {
        for (int i = 1; i <= N; i++) {
            if (i - 1 < 1) {
                dp[i][j] = dp[i + 1][j - 1];
            } else if (i + 1 > N) {
                dp[i][j] = dp[i - 1][j - 1];
            } else {
                dp[i][j] = dp[i + 1][j - 1] + dp[i - 1][j - 1];
            }
        }
    }
    return dp[M][P];
}

public static void main(String[] args) {
    System.out.println(robotWalkWaysDp(5, 2, 3, 3));
}
複製代碼

字符串正則匹配問題

給定字符串str,其中絕對不含有字符'.''*'。再給定字符串exp,其中能夠含有'.''*''*'字符不能是exp的首字符,而且任意兩個'*'字符不相鄰。exp中的'.'表明任何一個字符,exp中的'*'表示'*'的前一個字符能夠有0個或者多個。請寫一個函數,判斷str是否能被exp匹配。

舉例:

  • str="abc",exp="abc",返回truestr="abc",exp="a.c",exp中單個'.'能夠表明任意字符,因此返回true
  • str="abcd",exp=".*"。exp中'*'的前一個字符是'.',因此可表示任意數量的'.'字符,當exp是"...."時與"abcd"匹配,返回true
  • str="",exp="..*"。exp中'*'的前一個字符是'.',可表示任意數量的'.'字符,可是".*"以前還有一個'.'字符,該字符不受'*'的影響,因此str起碼有一個字符才能被exp匹配。因此返回false

暴力嘗試

定義一個方法bool match(char[] str, int i, char[] exp, int j),表示str的下標i ~ str.length部分可否和exp的下標j ~ exp.length部分匹配,分狀況討論以下:

  1. 若是j到了exp.lengthi還沒到str.length,返回false,不然返回true

  2. 若是ij都沒到右邊界,而且j的後一個字符不是*或者越界,那麼只有當str[i]=exp[j]exp[j]='.'時,ij才同時右移繼續比較match(str, i+1, exp, j+1),不然返回false

  3. 若是ij都沒到右邊界,而且j後一個字符是*,這時右有兩種狀況:

    1. str[i] = exp[j]exp[j]='.'。好比a*能夠匹配空串也能夠匹配一個a,若是str[i]以後還有連續的相同字符,那麼a*還能夠匹配多個,無論是哪一種狀況,將匹配後右移的ij交給子過程match

    2. str[i] != exp[j]exp[j] != ‘.’ ,那麼exp[j]*只能選擇匹配空串。

  4. 若是i到了str.lengthj還沒到exp.length,那麼j以後的字符只能是a*b*c*.*的形式,也就是一個字符後必須跟一個*的形式,這個檢驗過程一樣能夠交給match來作

示例代碼:

public static boolean match(char[] s, int i, char[] e, int j) {
    if (j == e.length) {
        return i == s.length;
    }
    //j下一個越界或者j下一個不是*
    if (j + 1 == e.length || e[j + 1] != '*') {
        if (i != s.length && s[i] == e[j] || e[j] == '.') {
            return match(s, i + 1, e, j + 1);
        }
        return false;
    }
    //j下一個不越界而且j下一個是*
    while (i != s.length && s[i] == e[j] || e[j] == '.') {
        if (match(s, i, e, j + 2)) {
            return true;
        }
        i++;
    }
    //若是上面的while是由於 s[i]!=e[j] 而中止的
    return match(s, i, e, j + 2);
}

public static boolean isMatch(String str, String exp) {
    if (str == null || exp == null) {
        return false;
    }
    char[] s = str.toCharArray();
    char[] e = exp.toCharArray();
    return match(s, 0, e, 0);
}

public static void main(String[] args) {
    System.out.println(isMatch("abbbbc","a.*b*c"));//T
    System.out.println(isMatch("abbbbc","a.*bbc"));//T
    System.out.println(isMatch("abbbbc","a.bbc"));//F
    System.out.println(isMatch("abbbbc","a.bbbc"));//T
}
複製代碼

動態規劃

match的參數列表中只有ij是變化的,也就是說只要肯定了ij就能對應肯定一個match的狀態,畫出二維表並將base case對應位置狀態值標註出來:

再看廣泛位置(i,j)的依賴,第6行的if代表(i,j)可能依賴(i+1, j+1),第13行的while代表(i,j)可能依賴(i, j+2)(i+1, j+2)(i+2, j+2)、……、(s.length-1, j+2)

你會發現(i,j)依賴它下面一行和右邊相鄰兩列的狀態,也就是說要想推出廣泛位置的狀態值,起碼須要最後一行、最後一列和倒數第二列上的狀態值。而base case僅爲咱們提供了最後一列的狀態值,主過程match(e, 0, s, 0)對應(0,0)位置的狀態值,咱們須要推出整張表全部位置的狀態值才行。

這時就要回歸題意了,看倒數第二列和最後一行上的狀態有什麼特殊含義。

首先最後一行表示i到了str.length,此時若是j還沒走完exp的話,從j開始到末尾的字符必須知足字符*字符*字符*的範式才返回true。所以最後一行狀態值易求:

而對於倒數第二列,表示j來到了exp的末尾字符,此時若是i若是在str末尾字符以前,那麼也是直接返回false的:

那麼接下來就只剩下(str.length-1, exp.length-1)這個位置的狀態值了,該位置標明i來到了str的末尾字符,j來到了exp的末尾字符,只有當這兩個字符相等或exp的末尾字符爲.才返回true不然false,也就是說該狀態能夠直接經過輸入參數strexp計算,它不依賴其餘狀態。二維表的初始化至此所有完成。

示例代碼:

public static boolean isMatch(String str, String exp) {
    if (str == null || exp == null) {
        return false;
    }
    return matchDp(str, exp);
}

public static boolean matchDp(String str, String exp) {
    if (str == null || exp == null) {
        return false;
    }
    char s[] = str.toCharArray();
    char e[] = exp.toCharArray();
    boolean[][] dpMap = initDpMap(s, e);

    //從倒數第二行開始推,每一行從右向左推
    for (int i = s.length - 1; i > -1; i--) {
        for (int j = e.length - 2; j > -1; j--) {
            if (e[j + 1] != '*') {
                dpMap[i][j] = (s[i] == e[j] || e[j] == '.') && dpMap[i + 1][j + 1];
            } else {
                int tmp = i;
                while (tmp != s.length && (s[tmp] == e[j] || e[j] == '.')) {
                    if (dpMap[tmp][j + 2]) {
                        dpMap[i][j] = true;
                        break;
                    }
                    tmp++;
                }
                if (dpMap[i][j] != true) {
                    dpMap[i][j] = dpMap[i][j + 2];
                }
            }
        }
    }
    return dpMap[0][0];
}

public static boolean[][] initDpMap(char[] s, char[] e) {
    boolean[][] dpMap = new boolean[s.length + 1][e.length + 1];
    //last column
    dpMap[s.length][e.length] = true;
    //last row -> i=s.length-1
    for (int j = e.length - 2; j >= 0; j = j - 2) {
        if (e[j] != '*' && e[j + 1] == '*') {
            dpMap[s.length - 1][j] = true;
        } else {
            break;
        }
    }
    //(str.length-1, e.length-1)
    if (s[s.length - 1] == e[e.length - 1] || e[e.length - 1] == '.') {
        dpMap[s.length - 1][e.length - 1] = true;
    }
    return dpMap;
}
複製代碼

緩存結構的設計

設計能夠變動的緩存結構(LRU)

設計一種緩存結構,該結構在構造時肯定大小,假設大小爲K,並有兩個功能:set(key,value):將記錄(key,value)插入該結構。get(key):返回key對應的value值。

【要求】

  • set和get方法的時間複雜度爲O(1)。
  • 某個key的set或get操做一旦發生,認爲這個key的記錄成了最常用的。
  • 當緩存的大小超過K時,移除最不常用的記錄,即set或get最久遠的。

【舉例】

假設緩存結構的實例是cache,大小爲3,並依次發生以下行爲:

  1. cache.set("A",1)。最常用的記錄爲("A",1)。
  2. cache.set("B",2)。最常用的記錄爲("B",2),("A",1)變爲最不常常的。
  3. cache.set("C",3)。最常用的記錄爲("C",2),("A",1)仍是最不常常的。
  4. cache.get("A")。最常用的記錄爲("A",1),("B",2)變爲最不常常的。
  5. cache.set("D",4)。大小超過了3,因此移除此時最不常用的記錄("B",2),加入記錄 ("D",4),而且爲最常用的記錄,而後("C",2)變爲最不常用的記錄

設計思路:使用一個哈希表和雙向鏈表

示例代碼:

package top.zhenganwen.structure;

import java.util.HashMap;

public class LRU {

    public static class Node<K,V>{
        K key;
        V value;
        Node<K,V> prev;
        Node<K, V> next;

        public Node(K key, V value) {
            this.key = key;
            this.value = value;
            this.prev = null;
            this.next = null;
        }
    }

    /** * the head is the oldest record and the tail is the newest record * * the add() will append the record to tail * * @param <K> key * @param <V> value */
    public static class DoubleLinkedList<K,V>{
        Node<K,V> head;
        Node<K, V> tail;
        public DoubleLinkedList() {
            this.head = null;
            this.tail = null;
        }

        public void add(Node<K,V> node){
            if (node == null) {
                return;
            }
            if (this.head == null) {
                this.head = node;
                this.tail = node;
            } else {
                this.tail.next = node;
                node.prev = this.tail;
                this.tail = node;
            }
        }

        public void moveToTail(Node<K,V> node){
            if (node == this.tail) {
                return;
            }
            if (node == this.head) {
                Node<K, V> newHead = node.next;
                newHead.prev = null;
                this.head = newHead;

                node.next = null;
                node.prev = this.tail;
                this.tail.next = node;
                this.tail = node;
            } else {
                node.prev.next = node.next;
                node.next.prev = node.prev;

                node.next=null;
                node.prev=this.tail;
                this.tail.next = node;
                this.tail = node;
            }
        }

        public K removeHead() {
            if (this.head != null) {
                K deletedK = this.head.key;
                if (this.head == this.tail) {
                    this.head = null;
                    this.tail = null;
                } else {
                    Node<K, V> newHead = this.head.next;
                    newHead.prev = null;
                    this.head = newHead;
                }
                return deletedK;
            }
            return null;
        }
    }

    public static class MyCache<K,V>{
        HashMap<K, Node<K, V>> map = new HashMap<>();
        DoubleLinkedList list = new DoubleLinkedList();
        int capacity;
        public MyCache(int capacity) {
            this.capacity = capacity;
        }

        public void set(K key, V value) {
            if (map.containsKey(key)) {
                //swap value

                //update map
                Node<K, V> node = map.get(key);
                node.value = value;
                map.put(key, node);
                //update list
                list.moveToTail(node);

            } else {
                //save record

                //if full,remove the oldest first and then save
                if (map.size() == this.capacity) {
                    K deletedK = (K) list.removeHead();
                    map.remove(deletedK);
                }
                Node<K, V> record = new Node<>(key, value);
                map.put(key, record);
                list.add(record);
            }
        }

        public V get(K key) {
            if (map.containsKey(key)) {
                Node<K, V> target = map.get(key);
                list.moveToTail(target);
                return target.value;
            } else {
                return null;
            }
        }
    }

    public static void main(String[] args) {
        MyCache<String, Integer> myCache = new MyCache<>(3);
        myCache.set("A", 1);
        myCache.set("B", 2);
        myCache.set("C", 3);
        System.out.println(myCache.get("A"));
        myCache.set("D", 4);
        System.out.println(myCache.get("B"));
    }
}
複製代碼

面試技巧:

在刷題時,若是感受這個題明顯在30分鐘內解不出來就放棄,由於面試給出的題目通常會讓你在30份內解出。

在面試時若是碰到本身遇到過的題也要裝做沒遇到過,僞裝一番苦思冥想、和麪試官溝通細節,而後忽然想通了的樣子。

LFU

LFU也是一種經典的緩存結構,只不過它是以key的訪問頻度做爲緩存替換依據的。

舉例:set("A",Data)將會在LFU結構中放入一條key爲「A」的記錄,並將該記錄的使用頻度置爲1,後續的set("A",newData)get("A")都會將該key對應的記錄的使用頻度加1;當該結構容量已滿還嘗試往裏添加記錄時,會先將結構中使用頻度最少的記錄刪除,再將新的記錄添加進去。

設計思路:使用一個哈希表和一個二維雙向鏈表(鏈表中包含鏈表)

示例代碼:

import java.util.HashMap;

public class LFUCache<K,V>{

    /** * Save all record */
    private HashMap<K, Record<K,V>> recordMap;
    /** * The reference of the FrequencyList whose frequency is the lowest */
    private FrequencyList headList;
    /** * Save what FrequencyList a record belongs to */
    private HashMap<Record,FrequencyList> listOfRecord;
    /** * How many recordMap the LFUCache can contain */
    private int capacity;
    /** * how many recordMap has been saved */
    private int size;

    public LFUCache(int capacity) {
        this.recordMap = new HashMap();
        this.listOfRecord = new HashMap<>();
        this.headList = null;
        this.capacity = capacity;
        this.size = 0;
    }

    /** * add or update a record * @param key * @param value */
    public void set(K key, V value) {
        //update
        if (this.recordMap.containsKey(key)) {
            //update value and frequency
            Record<K, V> record = recordMap.get(key);
            record.value = value;
            record.frequency++;
            //adjust the record's position in FrequencyList
            adjust(record, listOfRecord.get(record));
        } else {
            //add
            if (size == capacity) {
                //delete
                recordMap.remove(headList.tail.key);
                headList.deleteRecord(headList.tail);
                size--;
                modifyFrequencyList(headList);
            }
            Record<K, V> newRecord = new Record<>(key, value);
            recordMap.put(key, newRecord);
            size++;
            if (headList == null) {
                headList = new FrequencyList(newRecord);
            } else if (headList.head.frequency != 1) {
                FrequencyList frequencyList = new FrequencyList(newRecord);
                headList.prev = frequencyList;
                frequencyList.next = headList;
                frequencyList.prev = null;
                headList = frequencyList;
            } else {
                headList.addRecordToHead(newRecord);
            }
            listOfRecord.put(newRecord, headList);
        }
    }

    /** * get a record by a key,return null if not exists * @param key * @return */
    public V get(K key) {
        if (!recordMap.containsKey(key)) {
            return null;
        }
        Record<K, V> record = recordMap.get(key);
        record.frequency++;
        adjust(record, listOfRecord.get(record));
        return record.value;
    }

    /** * When the record's frequency changed,split it from its current * FrequencyList and insert to another one * * @param record * @param frequencyList */
    private void adjust(Record<K, V> record, FrequencyList frequencyList) {
        //split
        frequencyList.deleteRecord(record);
        boolean deleted = modifyFrequencyList(frequencyList);
        //insert to anther one
        FrequencyList prevList = frequencyList.prev;
        FrequencyList nextList = frequencyList.next;
        if (nextList != null && record.frequency == nextList.head.frequency) {
            nextList.addRecordToHead(record);
            listOfRecord.put(record, nextList);
        } else {
            FrequencyList newList = new FrequencyList(record);
            if (prevList == null) {
                if (nextList != null) {
                    nextList.prev = newList;
                }
                newList.next = nextList;
                newList.prev = null;
                headList = newList;
            } else if (nextList == null) {
                prevList.next = newList;
                newList.prev = prevList;
                newList.next = null;
            } else {
                prevList.next = newList;
                newList.prev = prevList;
                newList.next = nextList;
                nextList.prev = newList;
            }
            listOfRecord.put(record, newList);
        }
    }

    /** * return whether the frequencyList is deleted * @param frequencyList * @return */
    private boolean modifyFrequencyList(FrequencyList frequencyList) {
        if (!frequencyList.isEmpty()) {
            return false;
        }
        if (frequencyList.prev == null) {
            headList = frequencyList.next;
            if (headList != null) {
                headList.prev = null;
            }
        } else if (frequencyList.next == null) {
            frequencyList.prev.next = null;
        } else {
            frequencyList.prev.next = frequencyList.next;
            frequencyList.next.prev = frequencyList.prev;
        }
        return true;
    }

    /** * The Record can be design to Record<K,V> or Record<V> used * to encapsulate data * @param <K> key * @param <V> value */
    private class Record<K,V> {
        K key;
        V value;
        /** * up->the predecessor pointer * down->the successor pointer */
        Record<K, V> up;
        Record<K, V> down;
        /** * the frequency of use */
        int frequency;

        /** * when the record was created , set the frequency to 1 * * @param key * @param value */
        public Record(K key, V value) {
            this.key = key;
            this.value = value;
            this.frequency = 1;
        }
    }

    /** * The FrequencyList save a series of Records that * has the same frequency */
    private class FrequencyList {

        /** * prev->the predecessor pointer * next->the successor pointer */
        FrequencyList prev;
        FrequencyList next;
        /** * The reference of the internal RecordList's head and tail */
        Record<K,V> head;
        Record<K,V> tail;

        public FrequencyList(Record<K, V> record) {
            this.head = record;
            this.tail = record;
        }

        public void addRecordToHead(Record<K, V> record) {
            head.up = record;
            record.down = head;
            head = record;
        }

        public boolean isEmpty() {
            return head == null;
        }

        public void deleteRecord(Record<K,V> record) {
            if (head == tail) {
                head = null;
                tail = null;
            } else if (record == head) {
                head=head.down;
                head.up = null;
            } else if (record == tail) {
                tail = tail.up;
                tail.down = null;
            } else {
                record.up.down = record.down;
                record.down.up = record.up;
            }
        }
    }

    public static void main(String[] args) {
        LFUCache<String, Integer> cache = new LFUCache<>(3);
        cache.set("A", 1);
        cache.set("A", 1);
        cache.set("A", 1);
        cache.set("B", 2);
        cache.set("B", 2);
        cache.set("C", 3);
        cache.set("D", 4);
        System.out.println("break point");
    }
}
複製代碼

附錄

手寫二叉搜索樹

下列代碼來源於github

AbstractBinarySearchTree

/** * Not implemented by zhenganwen * * Abstract binary search tree implementation. Its basically fully implemented * binary search tree, just template method is provided for creating Node (other * trees can have slightly different nodes with more info). This way some code * from standart binary search tree can be reused for other kinds of binary * trees. * * @author Ignas Lelys * @created Jun 29, 2011 * */
public class AbstractBinarySearchTree {

    /** Root node where whole tree starts. */
    public Node root;

    /** Tree size. */
    protected int size;

    /** * Because this is abstract class and various trees have different * additional information on different nodes subclasses uses this abstract * method to create nodes (maybe of class {@link Node} or maybe some * different node sub class). * * @param value * Value that node will have. * @param parent * Node's parent. * @param left * Node's left child. * @param right * Node's right child. * @return Created node instance. */
    protected Node createNode(int value, Node parent, Node left, Node right) {
        return new Node(value, parent, left, right);
    }

    /** * Finds a node with concrete value. If it is not found then null is * returned. * * @param element * Element value. * @return Node with value provided, or null if not found. */
    public Node search(int element) {
        Node node = root;
        while (node != null && node.value != null && node.value != element) {
            if (element < node.value) {
                node = node.left;
            } else {
                node = node.right;
            }
        }
        return node;
    }

    /** * Insert new element to tree. * * @param element * Element to insert. */
    public Node insert(int element) {
        if (root == null) {
            root = createNode(element, null, null, null);
            size++;
            return root;
        }

        Node insertParentNode = null;
        Node searchTempNode = root;
        while (searchTempNode != null && searchTempNode.value != null) {
            insertParentNode = searchTempNode;
            if (element < searchTempNode.value) {
                searchTempNode = searchTempNode.left;
            } else {
                searchTempNode = searchTempNode.right;
            }
        }

        Node newNode = createNode(element, insertParentNode, null, null);
        if (insertParentNode.value > newNode.value) {
            insertParentNode.left = newNode;
        } else {
            insertParentNode.right = newNode;
        }

        size++;
        return newNode;
    }

    /** * Removes element if node with such value exists. * * @param element * Element value to remove. * * @return New node that is in place of deleted node. Or null if element for * delete was not found. */
    public Node delete(int element) {
        Node deleteNode = search(element);
        if (deleteNode != null) {
            return delete(deleteNode);
        } else {
            return null;
        }
    }

    /** * Delete logic when node is already found. * * @param deleteNode * Node that needs to be deleted. * * @return New node that is in place of deleted node. Or null if element for * delete was not found. */
    protected Node delete(Node deleteNode) {
        if (deleteNode != null) {
            Node nodeToReturn = null;
            if (deleteNode != null) {
                if (deleteNode.left == null) {
                    nodeToReturn = transplant(deleteNode, deleteNode.right);
                } else if (deleteNode.right == null) {
                    nodeToReturn = transplant(deleteNode, deleteNode.left);
                } else {
                    Node successorNode = getMinimum(deleteNode.right);
                    if (successorNode.parent != deleteNode) {
                        transplant(successorNode, successorNode.right);
                        successorNode.right = deleteNode.right;
                        successorNode.right.parent = successorNode;
                    }
                    transplant(deleteNode, successorNode);
                    successorNode.left = deleteNode.left;
                    successorNode.left.parent = successorNode;
                    nodeToReturn = successorNode;
                }
                size--;
            }
            return nodeToReturn;
        }
        return null;
    }

    /** * Put one node from tree (newNode) to the place of another (nodeToReplace). * * @param nodeToReplace * Node which is replaced by newNode and removed from tree. * @param newNode * New node. * * @return New replaced node. */
    private Node transplant(Node nodeToReplace, Node newNode) {
        if (nodeToReplace.parent == null) {
            this.root = newNode;
        } else if (nodeToReplace == nodeToReplace.parent.left) {
            nodeToReplace.parent.left = newNode;
        } else {
            nodeToReplace.parent.right = newNode;
        }
        if (newNode != null) {
            newNode.parent = nodeToReplace.parent;
        }
        return newNode;
    }

    /** * @param element * @return true if tree contains element. */
    public boolean contains(int element) {
        return search(element) != null;
    }

    /** * @return Minimum element in tree. */
    public int getMinimum() {
        return getMinimum(root).value;
    }

    /** * @return Maximum element in tree. */
    public int getMaximum() {
        return getMaximum(root).value;
    }

    /** * Get next element element who is bigger than provided element. * * @param element * Element for whom descendand element is searched * @return Successor value. */
    // TODO Predecessor
    public int getSuccessor(int element) {
        return getSuccessor(search(element)).value;
    }

    /** * @return Number of elements in the tree. */
    public int getSize() {
        return size;
    }

    /** * Tree traversal with printing element values. In order method. */
    public void printTreeInOrder() {
        printTreeInOrder(root);
    }

    /** * Tree traversal with printing element values. Pre order method. */
    public void printTreePreOrder() {
        printTreePreOrder(root);
    }

    /** * Tree traversal with printing element values. Post order method. */
    public void printTreePostOrder() {
        printTreePostOrder(root);
    }

    /*-------------------PRIVATE HELPER METHODS-------------------*/

    private void printTreeInOrder(Node entry) {
        if (entry != null) {
            printTreeInOrder(entry.left);
            if (entry.value != null) {
                System.out.println(entry.value);
            }
            printTreeInOrder(entry.right);
        }
    }

    private void printTreePreOrder(Node entry) {
        if (entry != null) {
            if (entry.value != null) {
                System.out.println(entry.value);
            }
            printTreeInOrder(entry.left);
            printTreeInOrder(entry.right);
        }
    }

    private void printTreePostOrder(Node entry) {
        if (entry != null) {
            printTreeInOrder(entry.left);
            printTreeInOrder(entry.right);
            if (entry.value != null) {
                System.out.println(entry.value);
            }
        }
    }

    protected Node getMinimum(Node node) {
        while (node.left != null) {
            node = node.left;
        }
        return node;
    }

    protected Node getMaximum(Node node) {
        while (node.right != null) {
            node = node.right;
        }
        return node;
    }

    protected Node getSuccessor(Node node) {
        // if there is right branch, then successor is leftmost node of that
        // subtree
        if (node.right != null) {
            return getMinimum(node.right);
        } else { // otherwise it is a lowest ancestor whose left child is also
            // ancestor of node
            Node currentNode = node;
            Node parentNode = node.parent;
            while (parentNode != null && currentNode == parentNode.right) {
                // go up until we find parent that currentNode is not in right
                // subtree.
                currentNode = parentNode;
                parentNode = parentNode.parent;
            }
            return parentNode;
        }
    }

    // -------------------------------- TREE PRINTING
    // ------------------------------------

    public void printTree() {
        printSubtree(root);
    }

    public void printSubtree(Node node) {
        if (node.right != null) {
            printTree(node.right, true, "");
        }
        printNodeValue(node);
        if (node.left != null) {
            printTree(node.left, false, "");
        }
    }

    private void printNodeValue(Node node) {
        if (node.value == null) {
            System.out.print("<null>");
        } else {
            System.out.print(node.value.toString());
        }
        System.out.println();
    }

    private void printTree(Node node, boolean isRight, String indent) {
        if (node.right != null) {
            printTree(node.right, true, indent + (isRight ? " " : " | "));
        }
        System.out.print(indent);
        if (isRight) {
            System.out.print(" /");
        } else {
            System.out.print(" \\");
        }
        System.out.print("----- ");
        printNodeValue(node);
        if (node.left != null) {
            printTree(node.left, false, indent + (isRight ? " | " : " "));
        }
    }

    public static class Node {
        public Node(Integer value, Node parent, Node left, Node right) {
            super();
            this.value = value;
            this.parent = parent;
            this.left = left;
            this.right = right;
        }

        public Integer value;
        public Node parent;
        public Node left;
        public Node right;

        public boolean isLeaf() {
            return left == null && right == null;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((value == null) ? 0 : value.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            Node other = (Node) obj;
            if (value == null) {
                if (other.value != null)
                    return false;
            } else if (!value.equals(other.value))
                return false;
            return true;
        }

    }
}
複製代碼

AbstractSelfBalancingBinarySearchTree

package advanced_class_03;

/** * Not implemented by zhenganwen * * Abstract class for self balancing binary search trees. Contains some methods * that is used for self balancing trees. * * @author Ignas Lelys * @created Jul 24, 2011 * */
public abstract class AbstractSelfBalancingBinarySearchTree extends AbstractBinarySearchTree {

    /** * Rotate to the left. * * @param node Node on which to rotate. * @return Node that is in place of provided node after rotation. */
    protected Node rotateLeft(Node node) {
        Node temp = node.right;
        temp.parent = node.parent;

        node.right = temp.left;
        if (node.right != null) {
            node.right.parent = node;
        }

        temp.left = node;
        node.parent = temp;

        // temp took over node's place so now its parent should point to temp
        if (temp.parent != null) {
            if (node == temp.parent.left) {
                temp.parent.left = temp;
            } else {
                temp.parent.right = temp;
            }
        } else {
            root = temp;
        }

        return temp;
    }

    /** * Rotate to the right. * * @param node Node on which to rotate. * @return Node that is in place of provided node after rotation. */
    protected Node rotateRight(Node node) {
        Node temp = node.left;
        temp.parent = node.parent;

        node.left = temp.right;
        if (node.left != null) {
            node.left.parent = node;
        }

        temp.right = node;
        node.parent = temp;

        // temp took over node's place so now its parent should point to temp
        if (temp.parent != null) {
            if (node == temp.parent.left) {
                temp.parent.left = temp;
            } else {
                temp.parent.right = temp;
            }
        } else {
            root = temp;
        }

        return temp;
    }

}
複製代碼

AVLTree

/** * Not implemented by zhenganwen * * AVL tree implementation. * * In computer science, an AVL tree is a self-balancing binary search tree, and * it was the first such data structure to be invented.[1] In an AVL tree, the * heights of the two child subtrees of any node differ by at most one. Lookup, * insertion, and deletion all take O(log n) time in both the average and worst * cases, where n is the number of nodes in the tree prior to the operation. * Insertions and deletions may require the tree to be rebalanced by one or more * tree rotations. * * @author Ignas Lelys * @created Jun 28, 2011 * */
public class AVLTree extends AbstractSelfBalancingBinarySearchTree {

    /** * @see trees.AbstractBinarySearchTree#insert(int) * * AVL tree insert method also balances tree if needed. Additional * height parameter on node is used to track if one subtree is higher * than other by more than one, if so AVL tree rotations is performed * to regain balance of the tree. */
    @Override
    public Node insert(int element) {
        Node newNode = super.insert(element);
        rebalance((AVLNode)newNode);
        return newNode;
    }

    /** * @see trees.AbstractBinarySearchTree#delete(int) */
    @Override
    public Node delete(int element) {
        Node deleteNode = super.search(element);
        if (deleteNode != null) {
            Node successorNode = super.delete(deleteNode);
            if (successorNode != null) {
                // if replaced from getMinimum(deleteNode.right) then come back there and update heights
                AVLNode minimum = successorNode.right != null ? (AVLNode)getMinimum(successorNode.right) : (AVLNode)successorNode;
                recomputeHeight(minimum);
                rebalance((AVLNode)minimum);
            } else {
                recomputeHeight((AVLNode)deleteNode.parent);
                rebalance((AVLNode)deleteNode.parent);
            }
            return successorNode;
        }
        return null;
    }

    /** * @see trees.AbstractBinarySearchTree#createNode(int, trees.AbstractBinarySearchTree.Node, trees.AbstractBinarySearchTree.Node, trees.AbstractBinarySearchTree.Node) */
    @Override
    protected Node createNode(int value, Node parent, Node left, Node right) {
        return new AVLNode(value, parent, left, right);
    }

    /** * Go up from inserted node, and update height and balance informations if needed. * If some node balance reaches 2 or -2 that means that subtree must be rebalanced. * * @param node Inserted Node. */
    private void rebalance(AVLNode node) {
        while (node != null) {

            Node parent = node.parent;

            int leftHeight = (node.left == null) ? -1 : ((AVLNode) node.left).height;
            int rightHeight = (node.right == null) ? -1 : ((AVLNode) node.right).height;
            int nodeBalance = rightHeight - leftHeight;
            // rebalance (-2 means left subtree outgrow, 2 means right subtree)
            if (nodeBalance == 2) {
                if (node.right.right != null) {
                    node = (AVLNode)avlRotateLeft(node);
                    break;
                } else {
                    node = (AVLNode)doubleRotateRightLeft(node);
                    break;
                }
            } else if (nodeBalance == -2) {
                if (node.left.left != null) {
                    node = (AVLNode)avlRotateRight(node);
                    break;
                } else {
                    node = (AVLNode)doubleRotateLeftRight(node);
                    break;
                }
            } else {
                updateHeight(node);
            }

            node = (AVLNode)parent;
        }
    }

    /** * Rotates to left side. */
    private Node avlRotateLeft(Node node) {
        Node temp = super.rotateLeft(node);

        updateHeight((AVLNode)temp.left);
        updateHeight((AVLNode)temp);
        return temp;
    }

    /** * Rotates to right side. */
    private Node avlRotateRight(Node node) {
        Node temp = super.rotateRight(node);

        updateHeight((AVLNode)temp.right);
        updateHeight((AVLNode)temp);
        return temp;
    }

    /** * Take right child and rotate it to the right side first and then rotate * node to the left side. */
    protected Node doubleRotateRightLeft(Node node) {
        node.right = avlRotateRight(node.right);
        return avlRotateLeft(node);
    }

    /** * Take right child and rotate it to the right side first and then rotate * node to the left side. */
    protected Node doubleRotateLeftRight(Node node) {
        node.left = avlRotateLeft(node.left);
        return avlRotateRight(node);
    }

    /** * Recomputes height information from the node and up for all of parents. It needs to be done after delete. */
    private void recomputeHeight(AVLNode node) {
        while (node != null) {
            node.height = maxHeight((AVLNode)node.left, (AVLNode)node.right) + 1;
            node = (AVLNode)node.parent;
        }
    }

    /** * Returns higher height of 2 nodes. */
    private int maxHeight(AVLNode node1, AVLNode node2) {
        if (node1 != null && node2 != null) {
            return node1.height > node2.height ? node1.height : node2.height;
        } else if (node1 == null) {
            return node2 != null ? node2.height : -1;
        } else if (node2 == null) {
            return node1 != null ? node1.height : -1;
        }
        return -1;
    }

    /** * Updates height and balance of the node. * * @param node Node for which height and balance must be updated. */
    private static final void updateHeight(AVLNode node) {
        int leftHeight = (node.left == null) ? -1 : ((AVLNode) node.left).height;
        int rightHeight = (node.right == null) ? -1 : ((AVLNode) node.right).height;
        node.height = 1 + Math.max(leftHeight, rightHeight);
    }

    /** * Node of AVL tree has height and balance additional properties. If balance * equals 2 (or -2) that node needs to be re balanced. (Height is height of * the subtree starting with this node, and balance is difference between * left and right nodes heights). * * @author Ignas Lelys * @created Jun 30, 2011 * */
    protected static class AVLNode extends Node {
        public int height;

        public AVLNode(int value, Node parent, Node left, Node right) {
            super(value, parent, left, right);
        }
    }

}
複製代碼

RedBlackTree

/** * Not implemented by zhenganwen * * Red-Black tree implementation. From Introduction to Algorithms 3rd edition. * * @author Ignas Lelys * @created May 6, 2011 * */
public class RedBlackTree extends AbstractSelfBalancingBinarySearchTree {

    protected enum ColorEnum {
        RED,
        BLACK
    };

    protected static final RedBlackNode nilNode = new RedBlackNode(null, null, null, null, ColorEnum.BLACK);

    /** * @see trees.AbstractBinarySearchTree#insert(int) */
    @Override
    public Node insert(int element) {
        Node newNode = super.insert(element);
        newNode.left = nilNode;
        newNode.right = nilNode;
        root.parent = nilNode;
        insertRBFixup((RedBlackNode) newNode);
        return newNode;
    }

    /** * Slightly modified delete routine for red-black tree. * * {@inheritDoc} */
    @Override
    protected Node delete(Node deleteNode) {
        Node replaceNode = null; // track node that replaces removedOrMovedNode
        if (deleteNode != null && deleteNode != nilNode) {
            Node removedOrMovedNode = deleteNode; // same as deleteNode if it has only one child, and otherwise it replaces deleteNode
            ColorEnum removedOrMovedNodeColor = ((RedBlackNode)removedOrMovedNode).color;

            if (deleteNode.left == nilNode) {
                replaceNode = deleteNode.right;
                rbTreeTransplant(deleteNode, deleteNode.right);
            } else if (deleteNode.right == nilNode) {
                replaceNode = deleteNode.left;
                rbTreeTransplant(deleteNode, deleteNode.left);
            } else {
                removedOrMovedNode = getMinimum(deleteNode.right);
                removedOrMovedNodeColor = ((RedBlackNode)removedOrMovedNode).color;
                replaceNode = removedOrMovedNode.right;
                if (removedOrMovedNode.parent == deleteNode) {
                    replaceNode.parent = removedOrMovedNode;
                } else {
                    rbTreeTransplant(removedOrMovedNode, removedOrMovedNode.right);
                    removedOrMovedNode.right = deleteNode.right;
                    removedOrMovedNode.right.parent = removedOrMovedNode;
                }
                rbTreeTransplant(deleteNode, removedOrMovedNode);
                removedOrMovedNode.left = deleteNode.left;
                removedOrMovedNode.left.parent = removedOrMovedNode;
                ((RedBlackNode)removedOrMovedNode).color = ((RedBlackNode)deleteNode).color;
            }

            size--;
            if (removedOrMovedNodeColor == ColorEnum.BLACK) {
                deleteRBFixup((RedBlackNode)replaceNode);
            }
        }

        return replaceNode;
    }

    /** * @see trees.AbstractBinarySearchTree#createNode(int, trees.AbstractBinarySearchTree.Node, trees.AbstractBinarySearchTree.Node, trees.AbstractBinarySearchTree.Node) */
    @Override
    protected Node createNode(int value, Node parent, Node left, Node right) {
        return new RedBlackNode(value, parent, left, right, ColorEnum.RED);
    }

    /** * {@inheritDoc} */
    @Override
    protected Node getMinimum(Node node) {
        while (node.left != nilNode) {
            node = node.left;
        }
        return node;
    }

    /** * {@inheritDoc} */
    @Override
    protected Node getMaximum(Node node) {
        while (node.right != nilNode) {
            node = node.right;
        }
        return node;
    }

    /** * {@inheritDoc} */
    @Override
    protected Node rotateLeft(Node node) {
        Node temp = node.right;
        temp.parent = node.parent;

        node.right = temp.left;
        if (node.right != nilNode) {
            node.right.parent = node;
        }

        temp.left = node;
        node.parent = temp;

        // temp took over node's place so now its parent should point to temp
        if (temp.parent != nilNode) {
            if (node == temp.parent.left) {
                temp.parent.left = temp;
            } else {
                temp.parent.right = temp;
            }
        } else {
            root = temp;
        }

        return temp;
    }

    /** * {@inheritDoc} */
    @Override
    protected Node rotateRight(Node node) {
        Node temp = node.left;
        temp.parent = node.parent;

        node.left = temp.right;
        if (node.left != nilNode) {
            node.left.parent = node;
        }

        temp.right = node;
        node.parent = temp;

        // temp took over node's place so now its parent should point to temp
        if (temp.parent != nilNode) {
            if (node == temp.parent.left) {
                temp.parent.left = temp;
            } else {
                temp.parent.right = temp;
            }
        } else {
            root = temp;
        }

        return temp;
    }


    /** * Similar to original transplant() method in BST but uses nilNode instead of null. */
    private Node rbTreeTransplant(Node nodeToReplace, Node newNode) {
        if (nodeToReplace.parent == nilNode) {
            this.root = newNode;
        } else if (nodeToReplace == nodeToReplace.parent.left) {
            nodeToReplace.parent.left = newNode;
        } else {
            nodeToReplace.parent.right = newNode;
        }
        newNode.parent = nodeToReplace.parent;
        return newNode;
    }

    /** * Restores Red-Black tree properties after delete if needed. */
    private void deleteRBFixup(RedBlackNode x) {
        while (x != root && isBlack(x)) {

            if (x == x.parent.left) {
                RedBlackNode w = (RedBlackNode)x.parent.right;
                if (isRed(w)) { // case 1 - sibling is red
                    w.color = ColorEnum.BLACK;
                    ((RedBlackNode)x.parent).color = ColorEnum.RED;
                    rotateLeft(x.parent);
                    w = (RedBlackNode)x.parent.right; // converted to case 2, 3 or 4
                }
                // case 2 sibling is black and both of its children are black
                if (isBlack(w.left) && isBlack(w.right)) {
                    w.color = ColorEnum.RED;
                    x = (RedBlackNode)x.parent;
                } else if (w != nilNode) {
                    if (isBlack(w.right)) { // case 3 sibling is black and its left child is red and right child is black
                        ((RedBlackNode)w.left).color = ColorEnum.BLACK;
                        w.color = ColorEnum.RED;
                        rotateRight(w);
                        w = (RedBlackNode)x.parent.right;
                    }
                    w.color = ((RedBlackNode)x.parent).color; // case 4 sibling is black and right child is red
                    ((RedBlackNode)x.parent).color = ColorEnum.BLACK;
                    ((RedBlackNode)w.right).color = ColorEnum.BLACK;
                    rotateLeft(x.parent);
                    x = (RedBlackNode)root;
                } else {
                    x.color = ColorEnum.BLACK;
                    x = (RedBlackNode)x.parent;
                }
            } else {
                RedBlackNode w = (RedBlackNode)x.parent.left;
                if (isRed(w)) { // case 1 - sibling is red
                    w.color = ColorEnum.BLACK;
                    ((RedBlackNode)x.parent).color = ColorEnum.RED;
                    rotateRight(x.parent);
                    w = (RedBlackNode)x.parent.left; // converted to case 2, 3 or 4
                }
                // case 2 sibling is black and both of its children are black
                if (isBlack(w.left) && isBlack(w.right)) {
                    w.color = ColorEnum.RED;
                    x = (RedBlackNode)x.parent;
                } else if (w != nilNode) {
                    if (isBlack(w.left)) { // case 3 sibling is black and its right child is red and left child is black
                        ((RedBlackNode)w.right).color = ColorEnum.BLACK;
                        w.color = ColorEnum.RED;
                        rotateLeft(w);
                        w = (RedBlackNode)x.parent.left;
                    }
                    w.color = ((RedBlackNode)x.parent).color; // case 4 sibling is black and left child is red
                    ((RedBlackNode)x.parent).color = ColorEnum.BLACK;
                    ((RedBlackNode)w.left).color = ColorEnum.BLACK;
                    rotateRight(x.parent);
                    x = (RedBlackNode)root;
                } else {
                    x.color = ColorEnum.BLACK;
                    x = (RedBlackNode)x.parent;
                }
            }

        }
    }

    private boolean isBlack(Node node) {
        return node != null ? ((RedBlackNode)node).color == ColorEnum.BLACK : false;
    }

    private boolean isRed(Node node) {
        return node != null ? ((RedBlackNode)node).color == ColorEnum.RED : false;
    }

    /** * Restores Red-Black tree properties after insert if needed. Insert can * break only 2 properties: root is red or if node is red then children must * be black. */
    private void insertRBFixup(RedBlackNode currentNode) {
        // current node is always RED, so if its parent is red it breaks
        // Red-Black property, otherwise no fixup needed and loop can terminate
        while (currentNode.parent != root && ((RedBlackNode) currentNode.parent).color == ColorEnum.RED) {
            RedBlackNode parent = (RedBlackNode) currentNode.parent;
            RedBlackNode grandParent = (RedBlackNode) parent.parent;
            if (parent == grandParent.left) {
                RedBlackNode uncle = (RedBlackNode) grandParent.right;
                // case1 - uncle and parent are both red
                // re color both of them to black
                if (((RedBlackNode) uncle).color == ColorEnum.RED) {
                    parent.color = ColorEnum.BLACK;
                    uncle.color = ColorEnum.BLACK;
                    grandParent.color = ColorEnum.RED;
                    // grandparent was recolored to red, so in next iteration we
                    // check if it does not break Red-Black property
                    currentNode = grandParent;
                } 
                // case 2/3 uncle is black - then we perform rotations
                else {
                    if (currentNode == parent.right) { // case 2, first rotate left
                        currentNode = parent;
                        rotateLeft(currentNode);
                    }
                    // do not use parent
                    parent.color = ColorEnum.BLACK; // case 3
                    grandParent.color = ColorEnum.RED;
                    rotateRight(grandParent);
                }
            } else if (parent == grandParent.right) {
                RedBlackNode uncle = (RedBlackNode) grandParent.left;
                // case1 - uncle and parent are both red
                // re color both of them to black
                if (((RedBlackNode) uncle).color == ColorEnum.RED) {
                    parent.color = ColorEnum.BLACK;
                    uncle.color = ColorEnum.BLACK;
                    grandParent.color = ColorEnum.RED;
                    // grandparent was recolored to red, so in next iteration we
                    // check if it does not break Red-Black property
                    currentNode = grandParent;
                }
                // case 2/3 uncle is black - then we perform rotations
                else {
                    if (currentNode == parent.left) { // case 2, first rotate right
                        currentNode = parent;
                        rotateRight(currentNode);
                    }
                    // do not use parent
                    parent.color = ColorEnum.BLACK; // case 3
                    grandParent.color = ColorEnum.RED;
                    rotateLeft(grandParent);
                }
            }

        }
        // ensure root is black in case it was colored red in fixup
        ((RedBlackNode) root).color = ColorEnum.BLACK;
    }

    protected static class RedBlackNode extends Node {
        public ColorEnum color;

        public RedBlackNode(Integer value, Node parent, Node left, Node right, ColorEnum color) {
            super(value, parent, left, right);
            this.color = color;
        }
    }

}
複製代碼
相關文章
相關標籤/搜索