算法初級面試題07——前綴樹應用、介紹和證實貪心策略、拼接字符串獲得最低字典序、切金條問題、項目收益最大化問題、隨時取中位數、宣講會安排

第六課主要介紹圖,不常常考,故今天先講第七課的內容,介紹比較常考的樹和貪心算法node

 

介紹前綴樹

何爲前綴樹? 如何生成前綴樹?面試

 

 

 

能夠查有多少個字符串以「be」爲前綴算法

若是要判斷有沒有「be」這個節點,每一個節點上加上一個數據項,有多少個字符串以當前節點結尾的(能夠查加了多少次特定字符串)。api

 


給一個字符串、返回多少個字符串以這個爲前綴數組

再加一個數據項,記錄該節點被劃過多少次。less

 


大概實現:dom

 


刪除邏輯:ide

根據path是否變爲0,來判斷是否繼續往下刪。ui

 


能夠解決如下問題:this

一個字符串類型的數組arr1,另外一個字符串類型的數組arr2。

arr2中有哪些字符,是arr1中出現的?請打印

arr2中有哪些字符,是做爲arr1中某個字符串前綴出現的?請打印

arr2中有哪些字符,是做爲arr1中某個字符串前綴出現的?請打印arr2中出現次數最大的前綴。

public class Code_01_TrieTree {

    public static class TrieNode {
        public int path;//多少個到達過節點
        public int end;//多少個以這個節點結尾
        public TrieNode[] nexts;//表示下一個節點的狀況

        public TrieNode() {
            path = 0;
            end = 0;
            //26個字母
            nexts = new TrieNode[26];
        }
    }

    public static class Trie {
        private TrieNode root;

        public Trie() {
            root = new TrieNode();
        }

        public void insert(String word) {
            if (word == null) {
                return;
            }
            char[] chs = word.toCharArray();
            TrieNode node = root;
            int index = 0;
            for (int i = 0; i < chs.length; i++) {
                index = chs[i] - 'a';//a index 0 -- b index 1 -- ...字母對應的
                if (node.nexts[index] == null) {//沒有路就新建出來
                    node.nexts[index] = new TrieNode();
                }
                node = node.nexts[index];
                node.path++;
            }
            node.end++;
        }

        public void delete(String word) {
            if (search(word) != 0) {
                char[] chs = word.toCharArray();
                TrieNode node = root;
                int index = 0;
                for (int i = 0; i < chs.length; i++) {
                    index = chs[i] - 'a';
                    if (--node.nexts[index].path == 0) {
                        node.nexts[index] = null;
                        return;
                    }
                    node = node.nexts[index];
                }
                node.end--;
            }
        }

        public int search(String word) {
            if (word == null) {
                return 0;
            }
            char[] chs = word.toCharArray();
            TrieNode node = root;
            int index = 0;
            for (int i = 0; i < chs.length; i++) {
                index = chs[i] - 'a';
                if (node.nexts[index] == null) {
                    return 0;
                }
                node = node.nexts[index];
            }
            return node.end;
        }

        public int prefixNumber(String pre) {
            if (pre == null) {
                return 0;
            }
            char[] chs = pre.toCharArray();
            TrieNode node = root;
            int index = 0;
            for (int i = 0; i < chs.length; i++) {
                index = chs[i] - 'a';
                if (node.nexts[index] == null) {
                    return 0;
                }
                node = node.nexts[index];
            }
            return node.path;
        }
    }

    public static void main(String[] args) {
        Trie trie = new Trie();
        System.out.println(trie.search("zuo"));
        trie.insert("zuo");
        System.out.println(trie.search("zuo"));
        trie.delete("zuo");
        System.out.println(trie.search("zuo"));
        trie.insert("zuo");
        trie.insert("zuo");
        trie.delete("zuo");
        System.out.println(trie.search("zuo"));
        trie.delete("zuo");
        System.out.println(trie.search("zuo"));
        trie.insert("zuoa");
        trie.insert("zuoac");
        trie.insert("zuoab");
        trie.insert("zuoad");
        trie.delete("zuoa");
        System.out.println(trie.search("zuoa"));
        System.out.println(trie.prefixNumber("zuo"));

    }

}

 

 

貪心策略

貪心策略正確性的證實,千萬不要糾結,由於糾結你能死去。

 

想練貪心,記貪心的點就夠了,寫對數器的方式來驗證。

 

不用去糾結哪一個策略,證實上哪一個是對的。這是一個大套路。

 

字典序介紹

例如abc和bce對比,想象成26進制的數進行對比。

長度不等的時候,把短的後面擴長來對比,後補最小的單位。

 

 

 


題目:給定一個字符串類型的數組strs,找到一種拼接方式,使得把全部字符串拼起來以後造成的字符串具備最低的字典序。

 

 

 

什麼叫作貪心?

定義一個指標,在這個指標下,把每個樣本分出一個優先來,按照優先大的先執行,優先小的後執行,這就是貪心。

貪心就是一件事情,貪心就是某一種簡潔的標準,在這個標準下把全部東西分出個一二三來,而後根據這個優先級,決定一種順序,就是貪心。

 

貪心案例:

貪心就是找距離目標最近的拿,無論最終的結果,只注重局部或者眼前的結果的方法

按照這種方法就是有可能喪失全局最優的

 

就比如兩我的分5個大餅,一次能夠拿一個或者兩個,不吃完不容許再拿

A:用貪心方法:第一次拿兩個,B拿一個,那麼B最終拿3個

這個就是貪心方法失敗的一個例子

 

有的時候貪心算法也是能夠獲得最優解的,好比仍是大餅的例子,可是如今總數是7個了

A繼續貪心,先兩個,再兩個就是4個了;

而B先1個,後2個,就只能是3個

 

介紹完貪心就回到題目:

若是對數組進行排序,再拼接一塊兒,那是不行的。

 

 

 


正確的排序策略:

str1作前綴str2跟後面。和相反着對比。(每一個字符串做爲前綴貼在一塊兒比較)

 


排序策略不成立的例子。

比較策略沒有傳遞性,是一個環。

 


有傳遞性是:

 


這個題比較策略就是貪心策略。要肯定的一件事情,排序策略是有傳遞性的。

 

把a.b看作成K進制的數,「abc」 「efg」 abcefg,其實就是abc向左移動了b這個數的長度位,再加入efg。

 

 

 

等式兩邊減b乘c,第二個式子減b乘a

而後再化簡。

 

展開後ac抹掉,同時除與b,a移過去右,c移過去左...

 

證實排序策略是有傳遞性,是一個對的排序:

 

還要證實用這種排序策略獲得的序列,就是字典序最小的。

先證實一件事情,序列裏面任意調換兩個字符串的順序後,都會產生更大的字典序。

因爲傳遞性:a.m1<=m1.a

 

 

一路下去,而後緊貼着b,b再和a交換。一路下來都是大於等於,那麼確定就只有比原始序列大。(因此原始序列是最經濟的)

 


這是一個貪心策略從提出策略,到證實正確,要付出較大的心血。

因此用對數器來驗證他對不對便可。

題目實現代碼:

 

public class Code_05_LowestLexicography {

    public static class MyComparator implements Comparator<String> {
        @Override
        public int compare(String a, String b) {
            return (a + b).compareTo(b + a);
        }
    }

    public static String lowestString(String[] strs) {
        if (strs == null || strs.length == 0) {
            return "";
        }
        //按照對比器排序
        Arrays.sort(strs, new MyComparator());
        String res = "";
        for (int i = 0; i < strs.length; i++) {
            res += strs[i];
        }
        return res;
    }

    public static void main(String[] args) {
        String[] strs1 = { "jibw", "ji", "jp", "bw", "jibw" };
        System.out.println(lowestString(strs1));

        String[] strs2 = { "ba", "b" };
        System.out.println(lowestString(strs2));

    }

}

 

 

 

 

題目:

一塊金條切成兩半,是須要花費和長度數值同樣的銅板的。好比長度爲20的 金條,無論切成長度多大的兩半,都要花費20個銅板。一羣人想整分整塊金 條,怎麼分最省銅板?

例如,給定數組{10,20,30},表明一共三我的,整塊金條長度爲 10+20+30=60. 金條要分紅10,20,30三個部分。 若是, 先把長度60的金條分紅10和50,花費60 再把長度50的金條分紅20和30,花費50 一共花費110銅板。

可是若是, 先把長度60的金條分紅30和30,花費60 再把長度30金條分紅10和20,花費30 一共花費90銅板。輸入一個數組,返回分割的最小代價。


標準的哈夫曼編碼問題

子節點合併在一塊兒的代價,是加起來的和。

最終要把全部的葉節點,生成一棵樹。


把數組變爲小根堆,而後從堆裏面取出兩個組成樹,在把組成的父結點,放入堆中...(如此反覆)最後代價就是產生的全部非葉節點。(貪心算法,每次從小根堆裏面拿最小的)

 


順序是從上面往下面切割。

當這種代價,是有子代價累加/乘,一種累什麼的,也可能給一個公式,這都有可能用哈夫曼編碼貪出來。

要具有起概念思想,用到其餘的題目中。

public class Code_02_Less_Money {

    public static int lessMoney(int[] arr) {
        //優先隊列是小根堆
        PriorityQueue<Integer> pQ = new PriorityQueue<>();
        for (int i = 0; i < arr.length; i++) {
            pQ.add(arr[i]);
        }
        int sum = 0;
        int cur = 0;
        while (pQ.size() > 1) {
            cur = pQ.poll() + pQ.poll();
            sum += cur;
            pQ.add(cur);
        }
        return sum;
    }

    public static class MinheapComparator implements Comparator<Integer> {
        //輸入參數的順序都爲 5  3
        //若是返回 正數(證實後面的比較小) 第二個參數排在前面   5 - 3 = 2  3排在前面
        //若是返回 負數(證實後面的比較大) 第一個參數排在前面   3 - 5 = -2 5排在前面
        //0表明兩個東西同樣大
        //總結:負1、正二(解決的是哪一個參數排在前面)
        @Override
        public int compare(Integer o1, Integer o2) {
            return o1 - o2; // < 0  o1 < o2  負數
        }

    }

    public static class MaxheapComparator implements Comparator<Integer> {

        //等因而-(o1-o2) = o2 - o1
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2 - o1; // <   o2 < o1
        }

    }

    public static void main(String[] args) {
        // solution
        int[] arr = { 6, 7, 8, 9 };
        System.out.println(lessMoney(arr));

        int[] arrForHeap = { 3, 5, 2, 7, 0, 1, 6, 4 };

        // min heap
        PriorityQueue<Integer> minQ1 = new PriorityQueue<>();
        for (int i = 0; i < arrForHeap.length; i++) {
            minQ1.add(arrForHeap[i]);
        }
        while (!minQ1.isEmpty()) {
            System.out.print(minQ1.poll() + " ");
        }
        System.out.println();

        // min heap use Comparator
        PriorityQueue<Integer> minQ2 = new PriorityQueue<>(new MinheapComparator());
        for (int i = 0; i < arrForHeap.length; i++) {
            minQ2.add(arrForHeap[i]);
        }
        while (!minQ2.isEmpty()) {
            System.out.print(minQ2.poll() + " ");
        }
        System.out.println();

        // max heap use Comparator
        PriorityQueue<Integer> maxQ = new PriorityQueue<>(new MaxheapComparator());
        for (int i = 0; i < arrForHeap.length; i++) {
            maxQ.add(arrForHeap[i]);
        }
        while (!maxQ.isEmpty()) {
            System.out.print(maxQ.poll() + " ");
        }

    }

}

 

項目收益最大化問題:

輸入: 參數1,正數數組costs 參數2,正數數組profits 參數3,正數k 參數4,正數m

costs[i]表示i號項目的花費 profits[i]表示i號項目在扣除花費以後還能掙到的錢(利潤) k表示你不能並行、只能串行的最多作k個項目 m表示你初始的資金

說明:你每作完一個項目,立刻得到的收益,能夠支持你去作下一個 項目。

輸出: 你最後得到的最大錢數。


兩個數組cost和profit,經過下標列出每一個項目的花費和利潤,作完項目後所得是cost+forfit,假設有資金W,一次只能作一個項目,最多作K個項目。輸出得到最大的錢數。

 

貪心策略。

 

 

首先項目類型包括花費和利潤,再根據花費放入小根堆中,而後看初始資金,在小根堆裏面依次彈出頭部,只要花費比W低的所有彈出。而後根據收益放入大根堆中,而後作大根堆的第一個項目。

 

當初始資金增長了,再看看小根堆中哪些項目能夠作的,繼續扔到大根堆中。一直作K個結束(或者大根堆裏面沒項目可作)。

public class Code_03_IPO {
    public static class Node {
        public int p;
        public int c;

        public Node(int p, int c) {
            this.p = p;
            this.c = c;
        }
    }

    public static class MinCostComparator implements Comparator<Node> {

        @Override
        public int compare(Node o1, Node o2) {
            return o1.c - o2.c;
        }

    }

    public static class MaxProfitComparator implements Comparator<Node> {

        @Override
        public int compare(Node o1, Node o2) {
            return o2.p - o1.p;
        }

    }

    public static int findMaximizedCapital(int k, int W, int[] Profits, int[] Capital) {
        Node[] nodes = new Node[Profits.length];
        for (int i = 0; i < Profits.length; i++) {
            nodes[i] = new Node(Profits[i], Capital[i]);
        }

        PriorityQueue<Node> minCostQ = new PriorityQueue<>(new MinCostComparator());
        PriorityQueue<Node> maxProfitQ = new PriorityQueue<>(new MaxProfitComparator());
        for (int i = 0; i < nodes.length; i++) {
            minCostQ.add(nodes[i]);
        }
        for (int i = 0; i < k; i++) {
            //先把能作的項目存在大根堆中
            while (!minCostQ.isEmpty() && minCostQ.peek().c <= W) {
                maxProfitQ.add(minCostQ.poll());
            }
            if (maxProfitQ.isEmpty()) {
                return W;
            }
            W += maxProfitQ.poll().p;
        }
        return W;
    }

}

 

隨時取中位數

一個數據流中,隨時能夠取得中位數(以前講過,初級2 020143)

 

不用堆的話,就用一個容器,吐出一個數就裝一個,由於無序的因此裝的時候也是無序的。當想要中位數的時候,要排個序,犧牲大。

 

 

準備兩個堆,一個大根堆一個小根堆。

第一個數進入大根堆,下一個數小於等於就進大根堆。

若是從大根堆彈出一個數,就拿最後的一個數和第一個數交換,而後heapsize界限-1。

 

 

看題目怎麼作:

先把數加入到大根堆中,若是發現兩個堆大小差值超過了1,就把較大的彈出一個放到另一個去。

若是要查中位數,大根堆堆頂和小根堆堆頂確定能計算出來,由於

大根堆收集了較小的2分之N個,堆頂是其中的最大值。

小根堆收集的是較大的2分之N個,堆頂是其中的最小值。

正好卡在中間的位置。

只要當前數不是小於等於大根堆的,就扔到小根堆中。

策略是固定的:

一、若是當前數小於等於大根堆的堆頂,到大根堆,不然小根堆

二、不平衡了拿出扔到少的堆裏面。

這樣隨時能夠拿到中位數。


堆很好用(調整隻和層數相關、o(logn)),幾乎搞定全部貪心的題目。(面試也常常考)

優先級隊列就是堆。

 

 

 

public class Code_04_MadianQuick {

    public static class MedianHolder {
        private PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(new MaxHeapComparator());
        private PriorityQueue<Integer> minHeap = new PriorityQueue<Integer>(new MinHeapComparator());

        private void modifyTwoHeapsSize() {
            if (this.maxHeap.size() == this.minHeap.size() + 2) {
                this.minHeap.add(this.maxHeap.poll());
            }
            if (this.minHeap.size() == this.maxHeap.size() + 2) {
                this.maxHeap.add(this.minHeap.poll());
            }
        }

        public void addNumber(int num) {
            if (this.maxHeap.isEmpty()) {
                this.maxHeap.add(num);
                return;
            }
            if (this.maxHeap.peek() >= num) {
                this.maxHeap.add(num);
            } else {
                if (this.minHeap.isEmpty()) {
                    this.minHeap.add(num);
                    return;
                }        
                if (this.minHeap.peek() > num) {
                    this.maxHeap.add(num);
                } else {
                    this.minHeap.add(num);
                }
            }
            modifyTwoHeapsSize();
        }

        public Integer getMedian() {
            int maxHeapSize = this.maxHeap.size();
            int minHeapSize = this.minHeap.size();
            if (maxHeapSize + minHeapSize == 0) {
                return null;
            }
            Integer maxHeapHead = this.maxHeap.peek();
            Integer minHeapHead = this.minHeap.peek();
            //n & 1 判斷奇偶數
            //n&1是n和1作「按位與」運算
            //1的二進制只有末位是1,因此n&1就是隻保留n的末位(二進制).n&1就表示了n的奇偶性.
            if (((maxHeapSize + minHeapSize) & 1) == 0) {//兩個堆的數量相同
                //若是從數據流中讀出偶數個數值,那麼中位數就是全部數值排序以後中間兩個數的平均值。
                return (maxHeapHead + minHeapHead) / 2;
            }
            //若是大/小根堆數量多中間數就在大/小根堆的堆頂。
            return maxHeapSize > minHeapSize ? maxHeapHead : minHeapHead;
        }

    }

    public static class MaxHeapComparator implements Comparator<Integer> {
        @Override
        public int compare(Integer o1, Integer o2) {
            if (o2 > o1) {
                return 1;
            } else {
                return -1;
            }
        }
    }

    public static class MinHeapComparator implements Comparator<Integer> {
        @Override
        public int compare(Integer o1, Integer o2) {
            if (o2 < o1) {
                return 1;
            } else {
                return -1;
            }
        }
    }

    // for test
    public static int[] getRandomArray(int maxLen, int maxValue) {
        int[] res = new int[(int) (Math.random() * maxLen) + 1];
        for (int i = 0; i != res.length; i++) {
            res[i] = (int) (Math.random() * maxValue);
        }
        return res;
    }

    // for test, this method is ineffective but absolutely right
    public static int getMedianOfArray(int[] arr) {
        int[] newArr = Arrays.copyOf(arr, arr.length);
        Arrays.sort(newArr);
        int mid = (newArr.length - 1) / 2;
        if ((newArr.length & 1) == 0) {
            return (newArr[mid] + newArr[mid + 1]) / 2;
        } else {
            return newArr[mid];
        }
    }

    public static void printArray(int[] arr) {
        for (int i = 0; i != arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        boolean err = false;
        int testTimes = 200000;
        for (int i = 0; i != testTimes; i++) {
            int len = 30;
            int maxValue = 1000;
            int[] arr = getRandomArray(len, maxValue);
            MedianHolder medianHold = new MedianHolder();
            for (int j = 0; j != arr.length; j++) {
                medianHold.addNumber(arr[j]);
            }
            if (medianHold.getMedian() != getMedianOfArray(arr)) {
                err = true;
                printArray(arr);
                break;
            }
        }
        System.out.println(err ? "Oops..what a fuck!" : "today is a beautiful day^_^");

    }

}

 

 

宣講會安排

一些項目要佔用一個會議室宣講,會議室不能同時容納兩個項目的宣講。給你每個項目開始的時間和結束的時間(給你一個數組,裏面是一個個具體的項目),你來安排宣講的日程,要求會議室進行的宣講的場次最多。返回這個最多的宣講場次。

 

安排最先開始的先進行,是不行的,若是那項目持續一天,其餘項目都講不來了了。

 

按照持續時間短來安排,也得不到最優解。若是一個小項目卡在兩個大項目中間,阻礙了大項目的安排,也不是最優解。

 


根據哪一個項目早結束安排。

 

public class Code_06_BestArrange {

    public static class Program {
        public int start;
        public int end;

        public Program(int start, int end) {
            this.start = start;
            this.end = end;
        }
    }

    public static class ProgramComparator implements Comparator<Program> {

        @Override
        public int compare(Program o1, Program o2) {
            return o1.end - o2.end;
        }

    }

    public static int bestArrange(Program[] programs, int start) {
        Arrays.sort(programs, new ProgramComparator());
        int result = 0;
        for (int i = 0; i < programs.length; i++) {
            if (start <= programs[i].start) {
                result++;
                start = programs[i].end;
            }
        }
        return result;
    }

    public static void main(String[] args) {

    }

}

 

貪心策略就是經驗式的東西(依靠累計)。不用糾結於證實。

相關文章
相關標籤/搜索