日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

高级数据结构讲解与案例分析

發(fā)布時(shí)間:2023/11/28 生活经验 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 高级数据结构讲解与案例分析 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

然而,僅僅掌握好它們不足以應(yīng)付大廠的算法面試的。為了達(dá)到對(duì)時(shí)間和空間復(fù)雜度的理想要求,本節(jié)課探究高級(jí)數(shù)據(jù)結(jié)構(gòu),它們的實(shí)現(xiàn)要比那些常用的數(shù)據(jù)結(jié)構(gòu)要復(fù)雜得多。其中重點(diǎn)介紹:

  • 優(yōu)先隊(duì)列

  • 前綴樹

  • 線段樹

  • 樹狀數(shù)組

?

掌握好高級(jí)數(shù)據(jù)結(jié)構(gòu)的性質(zhì)以及所適用的場合,在分析問題的時(shí)候回歸本質(zhì),很多題目都能迎刃而解。

?

優(yōu)先隊(duì)列(Priority Queue)

?

特點(diǎn)

能保證每次取出的元素都是隊(duì)列中優(yōu)先級(jí)別最高的。優(yōu)先級(jí)別可以是自定義的,例如,數(shù)據(jù)的數(shù)值越大,優(yōu)先級(jí)越高;或者數(shù)據(jù)的數(shù)值越小,優(yōu)先級(jí)越高。優(yōu)先級(jí)別甚至可以通過各種復(fù)雜的計(jì)算得到。

?

應(yīng)用場景

從一堆雜亂無章的數(shù)據(jù)當(dāng)中按照一定的順序(或者優(yōu)先級(jí))逐步地篩選出部分乃至全部的數(shù)據(jù)。

?

舉例:任意一個(gè)數(shù)組,找出前?k?大的數(shù)。

?

解法 1:先對(duì)這個(gè)數(shù)組進(jìn)行排序,然后依次輸出前?k?大的數(shù),復(fù)雜度將會(huì)是?O(nlogn),其中,n?是數(shù)組的元素個(gè)數(shù)。這是一種直接的辦法。

?

解法 2:使用優(yōu)先隊(duì)列,復(fù)雜度優(yōu)化成?O(k + nlogk)。

當(dāng)數(shù)據(jù)量很大(即?n?很大),而?k?相對(duì)較小的時(shí)候,顯然,利用優(yōu)先隊(duì)列能有效地降低算法復(fù)雜度。因?yàn)橐页銮?k?大的數(shù),并不需要對(duì)所有的數(shù)進(jìn)行排序。

?

實(shí)現(xiàn)

優(yōu)先隊(duì)列的本質(zhì)是一個(gè)二叉堆結(jié)構(gòu)。堆在英文里叫?Binary Heap,它是利用一個(gè)數(shù)組結(jié)構(gòu)來實(shí)現(xiàn)的完全二叉樹。換句話說,優(yōu)先隊(duì)列的本質(zhì)是一個(gè)數(shù)組,數(shù)組里的每個(gè)元素既有可能是其他元素的父節(jié)點(diǎn),也有可能是其他元素的子節(jié)點(diǎn),而且,每個(gè)父節(jié)點(diǎn)只能有兩個(gè)子節(jié)點(diǎn),很像一棵二叉樹的結(jié)構(gòu)。

?

牢記下面優(yōu)先隊(duì)列有三個(gè)重要的性質(zhì)。

1. 數(shù)組里的第一個(gè)元素?array[0]?擁有最高的優(yōu)先級(jí)別。

2. 給定一個(gè)下標(biāo) i,那么對(duì)于元素 array[i] 而言:

  • 它的父節(jié)點(diǎn)所對(duì)應(yīng)的元素下標(biāo)是 (i-1)/2

  • 它的左孩子所對(duì)應(yīng)的元素下標(biāo)是 2×i + 1

  • 它的右孩子所對(duì)應(yīng)的元素下標(biāo)是 2×i + 2

3. 數(shù)組里每個(gè)元素的優(yōu)先級(jí)別都要高于它兩個(gè)孩子的優(yōu)先級(jí)別。

?

優(yōu)先隊(duì)列最基本的操作有兩個(gè)。

1. 向上篩選(sift?up / bubble up)

  • 當(dāng)有新的數(shù)據(jù)加入到優(yōu)先隊(duì)列中,新的數(shù)據(jù)首先被放置在二叉堆的底部。

  • 不斷進(jìn)行向上篩選的操作,即如果發(fā)現(xiàn)該數(shù)據(jù)的優(yōu)先級(jí)別比父節(jié)點(diǎn)的優(yōu)先級(jí)別還要高,那么就和父節(jié)點(diǎn)的元素相互交換,再接著往上進(jìn)行比較,直到無法再繼續(xù)交換為止。

? ? ? ?? ? ? ?

時(shí)間復(fù)雜度:由于二叉堆是一棵完全二叉樹,并假設(shè)堆的大小為?k,因此整個(gè)過程其實(shí)就是沿著樹的高度往上爬,所以只需要?O(logk)?的時(shí)間。

???????????

2. 向下篩選(sift down / bubble down)

  • 當(dāng)堆頂?shù)脑乇蝗〕鰰r(shí),要更新堆頂?shù)脑貋碜鳛橄乱淮伟凑諆?yōu)先級(jí)順序被取出的對(duì)象,需要將堆底部的元素放置到堆頂,然后不斷地對(duì)它執(zhí)行向下篩選的操作。

  • 將該元素和它的兩個(gè)孩子節(jié)點(diǎn)對(duì)比優(yōu)先級(jí),如果優(yōu)先級(jí)最高的是其中一個(gè)孩子,就將該元素和那個(gè)孩子進(jìn)行交換,然后反復(fù)進(jìn)行下去,直到無法繼續(xù)交換為止。

? ? ? ?? ? ? ?

時(shí)間復(fù)雜度:整個(gè)過程就是沿著樹的高度往下爬,所以時(shí)間復(fù)雜度也是?O(logk)。

?

因此,無論是添加新的數(shù)據(jù)還是取出堆頂?shù)脑?#xff0c;都需要?O(logk)?的時(shí)間。

?

初始化

優(yōu)先隊(duì)列的初始化是一個(gè)最重要的時(shí)間復(fù)雜度,是分析運(yùn)用優(yōu)先隊(duì)列性能時(shí)必不可少的,也是經(jīng)常容易弄錯(cuò)的地方。

?

舉例:有?n?個(gè)數(shù)據(jù),需要?jiǎng)?chuàng)建一個(gè)大小為?n?的堆。

?

誤區(qū):每當(dāng)把一個(gè)數(shù)據(jù)加入到堆里,都要對(duì)其執(zhí)行向上篩選的操作,這樣一來就是?O(nlogn)。

?

解法:在創(chuàng)建這個(gè)堆的過程中,二叉樹的大小是從?1?逐漸增長到?n?的,所以整個(gè)算法的復(fù)雜度經(jīng)過推導(dǎo),最終的結(jié)果是 O(n)。

?

? ? ? ??? ? ?

?

注意:算法面試中是不要求推導(dǎo)的,你只需要記住,初始化一個(gè)大小為?n?的堆,所需要的時(shí)間是?O(n)?即可。

?

例題分析

LeetCode 第?347?題:給定一個(gè)非空的整數(shù)數(shù)組,返回其中出現(xiàn)頻率前?k?高的元素。

?

說明:

  • 你可以假設(shè)給定的?k?總是合理的,且 1 ≤ k ≤ 數(shù)組中不相同的元素的個(gè)數(shù)。

  • 你的算法的時(shí)間復(fù)雜度必須優(yōu)于?O(nlogn) ,n?是數(shù)組的大小

示例:car,car,book,desk,desk,desk

?

解題思路

這道題的輸入是一個(gè)字符串?dāng)?shù)組,數(shù)組里的元素可能會(huì)重復(fù)一次甚至多次,要求按順序輸出前?k?個(gè)出現(xiàn)次數(shù)最多的字符串。

?

解這類求"前 k 個(gè)"的題目,關(guān)鍵是看如何定義優(yōu)先級(jí)以及優(yōu)先隊(duì)列中元素的數(shù)據(jù)結(jié)構(gòu)。

  • 題目中有”前?k?個(gè)“這樣的字眼,應(yīng)該很自然地聯(lián)想到優(yōu)先隊(duì)列。

  • 優(yōu)先級(jí)別可以由字符串出現(xiàn)的次數(shù)來決定,出現(xiàn)的次數(shù)越多,優(yōu)先級(jí)別越高,反之越低。

  • 統(tǒng)計(jì)詞頻的最佳數(shù)據(jù)結(jié)構(gòu)就是哈希表(Hash?Map),利用一個(gè)哈希表,就能快速地知道每個(gè)單詞出現(xiàn)的次數(shù)。

  • 將單詞和其出現(xiàn)的次數(shù)作為一個(gè)新的對(duì)象來構(gòu)建一個(gè)優(yōu)先隊(duì)列,那么這個(gè)問題就很輕而易舉地解決了。

?

建議:這道題是利用優(yōu)先隊(duì)列處理問題的典型,建議好好練習(xí)。

?

??????????????????????????????????Desk (3)

????????????????????????????????????/ ? ?\

?????????????????????????????car(2) ? book(1)?? ? ? ???

????????????????????????????????

?

圖(Graph)

?

基本知識(shí)點(diǎn)

圖可以說是所有數(shù)據(jù)結(jié)構(gòu)里面知識(shí)點(diǎn)最豐富的一個(gè),最基本的知識(shí)點(diǎn)如下。

  • 階(Order)、度:出度(Out-Degree)、入度(In-Degree)

  • 樹(Tree)、森林(Forest)、環(huán)(Loop)

  • 有向圖(Directed Graph)、無向圖(Undirected Graph)、完全有向圖、完全無向圖

  • 連通圖(Connected Graph)、連通分量(Connected Component)

  • 存儲(chǔ)和表達(dá)方式:鄰接矩陣(Adjacency Matrix)、鄰接鏈表(Adjacency List)

?

圍繞圖的算法也是五花八門。

  • 圖的遍歷:深度優(yōu)先、廣度優(yōu)先

  • 環(huán)的檢測(cè):有向圖、無向圖

  • 拓?fù)渑判?/p>

  • 最短路徑算法:Dijkstra、Bellman-Ford、Floyd Warshall

  • 連通性相關(guān)算法:Kosaraju、Tarjan、求解孤島的數(shù)量、判斷是否為樹

  • 圖的著色、旅行商問題等

?

以上的知識(shí)點(diǎn)只是圖論里的冰山一角,對(duì)于算法面試而言,完全不需要對(duì)每個(gè)知識(shí)點(diǎn)都一一掌握,而應(yīng)該有的放矢地進(jìn)行準(zhǔn)備。

?

必會(huì)知識(shí)點(diǎn)

根據(jù)長期的經(jīng)驗(yàn)總結(jié),以下的知識(shí)點(diǎn)是必須充分掌握并反復(fù)練習(xí)的。

  • 圖的存儲(chǔ)和表達(dá)方式:鄰接矩陣(Adjacency Matrix)、鄰接鏈表(Adjacency List)

  • 圖的遍歷:深度優(yōu)先、廣度優(yōu)先

  • 二部圖的檢測(cè)(Bipartite)、樹的檢測(cè)、環(huán)的檢測(cè):有向圖、無向圖

  • 拓?fù)渑判?/p>

  • 聯(lián)合-查找算法(Union-Find)

  • 最短路徑:Dijkstra、Bellman-Ford

?

其中,環(huán)的檢測(cè)、二部圖的檢測(cè)、樹的檢測(cè)以及拓?fù)渑判蚨际腔趫D的遍歷,尤其是深度優(yōu)先方式的遍歷。而遍歷可以在鄰接矩陣或者鄰接鏈表上進(jìn)行,所以掌握好圖的遍歷是重中之重!因?yàn)樗撬衅渌麍D論算法的基礎(chǔ)。

?

至于最短路徑算法,能區(qū)分它們的不同特點(diǎn),知道在什么情況下用哪種算法就很好了。對(duì)于有充足時(shí)間準(zhǔn)備的面試者,能熟練掌握它們的寫法當(dāng)然是最好的。

?

建議:LeetCode 里邊有許多關(guān)于圖論的算法題,而且都是非常經(jīng)典的題目,可以通過練習(xí)解題來熟練掌握必備知識(shí)。

?

?

例題分析

LeetCode 第?785?題:給定一個(gè)無向圖 graph,當(dāng)這個(gè)圖為二部圖時(shí)返回 true。

?

提示:如果能將一個(gè)圖的節(jié)點(diǎn)集合分割成兩個(gè)獨(dú)立的子集 A 和 B,并使圖中的每一條邊的兩個(gè)節(jié)點(diǎn)一個(gè)來自 A 集合,一個(gè)來自 B 集合,就將這個(gè)圖稱為二部圖。

?

解題思路

判斷一個(gè)給定的任意圖是否為二部圖,就必須要對(duì)該圖進(jìn)行一次遍歷:

  • 深度優(yōu)先

  • 廣度優(yōu)先

(關(guān)于深度優(yōu)先和廣度優(yōu)先算法,將在第?06?節(jié)課進(jìn)行詳細(xì)討論)。

?

二部圖,圖的所有頂點(diǎn)可以分成兩個(gè)子集 U 和 V,子集里的頂點(diǎn)互不直接相連,圖里面所有的邊,一頭連著子集 U 里的頂點(diǎn),一頭連著子集 V 里的頂點(diǎn)。

  1. 給圖里的頂點(diǎn)涂上顏色,子集?U?里的頂點(diǎn)都涂上紅色,子集?V?里的頂點(diǎn)都涂上藍(lán)色。

  2. 開始遍歷這個(gè)圖的所有頂點(diǎn),想象一下手里握有紅色和藍(lán)色的畫筆,每次交替地給遍歷當(dāng)中遇到的頂點(diǎn)涂上顏色。

  3. 如果這個(gè)頂點(diǎn)還沒有顏色,那就給它涂上顏色,然后換成另外一支畫筆。

  4. 下一個(gè)頂點(diǎn),如果發(fā)現(xiàn)這個(gè)頂點(diǎn)已經(jīng)涂上了顏色,而且顏色跟我手里畫筆的顏色不同,那么表示這個(gè)頂點(diǎn)它既能在子集?U?里,也能在子集?V?里。

  5. 所以,它不是一個(gè)二部圖。

?

前綴樹(Trie)

?

應(yīng)用場景

前綴樹被廣泛地運(yùn)用在字典查找當(dāng)中,也被稱為字典樹。

?

舉例:給定一系列字符串,這些字符串構(gòu)成了一種字典,要求你在這個(gè)字典當(dāng)中找出所有以“ABC”開頭的字符串。

?

解法 1:暴力搜索

直接遍歷一遍字典,然后逐個(gè)判斷每個(gè)字符串是否由“ABC”開頭。假設(shè)字典很大,有?N?個(gè)單詞,要對(duì)比的不是“ABC”,而是任意的,那不妨假設(shè)所要對(duì)比的開頭平均長度為?M,那么時(shí)間復(fù)雜度是?O(M×N)。

?

解法 2:前綴樹

如果用前綴樹頭幫助對(duì)字典的存儲(chǔ)進(jìn)行優(yōu)化,那么可以把搜索的時(shí)間復(fù)雜度下降為?O(M),其中?M?表示字典里最長的那個(gè)單詞的字符個(gè)數(shù),在很多情況下,字典里的單詞個(gè)數(shù)?N?是遠(yuǎn)遠(yuǎn)大于?M?的。因此,前綴樹在這種場合中是非常高效的。

?

經(jīng)典應(yīng)用

  1. 網(wǎng)站上的搜索框會(huì)羅列出以搜索文字作為開頭的相關(guān)搜索信息,這里運(yùn)用了前綴樹進(jìn)行后端的快速檢索。

  2. 漢字拼音輸入法的聯(lián)想輸出功能也運(yùn)用了前綴樹。

?

舉例:假如有一個(gè)字典,字典里面有如下詞:"A","to","tea","ted","ten","i","in","inn",每個(gè)單詞還能有自己的一些權(quán)重值,那么用前綴樹來構(gòu)建這個(gè)字典將會(huì)是如下的樣子:

?

? ? ? ??? ? ?

?

性質(zhì)

1.?每個(gè)節(jié)點(diǎn)至少包含兩個(gè)基本屬性。

  • children:數(shù)組或者集合,羅列出每個(gè)分支當(dāng)中包含的所有字符

  • isEnd:布爾值,表示該節(jié)點(diǎn)是否為某字符串的結(jié)尾

?

2. 前綴樹的根節(jié)點(diǎn)是空的

所謂空,即只利用到這個(gè)節(jié)點(diǎn)的?children?屬性,即只關(guān)心在這個(gè)字典里,有哪些打頭的字符。

?

3. 除了根節(jié)點(diǎn),其他所有節(jié)點(diǎn)都有可能是單詞的結(jié)尾,葉子節(jié)點(diǎn)一定都是單詞的結(jié)尾。

?

實(shí)現(xiàn)

前綴樹最基本的操作就是兩個(gè):創(chuàng)建和搜索。

?

1. 創(chuàng)建

  • 遍歷一遍輸入的字符串,對(duì)每個(gè)字符串的字符進(jìn)行遍歷

  • 從前綴樹的根節(jié)點(diǎn)開始,將每個(gè)字符加入到節(jié)點(diǎn)的?children?字符集當(dāng)中。

  • 如果字符集已經(jīng)包含了這個(gè)字符,則跳過。

  • 如果當(dāng)前字符是字符串的最后一個(gè),則把當(dāng)前節(jié)點(diǎn)的?isEnd?標(biāo)記為真。

由上,創(chuàng)建的方法很直觀。

?

前綴樹真正強(qiáng)大的地方在于,每個(gè)節(jié)點(diǎn)還能用來保存額外的信息,比如可以用來記錄擁有相同前綴的所有字符串。因此,當(dāng)用戶輸入某個(gè)前綴時(shí),就能在?O(1)?的時(shí)間內(nèi)給出對(duì)應(yīng)的推薦字符串。

?

2. 搜索

與創(chuàng)建方法類似,從前綴樹的根節(jié)點(diǎn)出發(fā),逐個(gè)匹配輸入的前綴字符,如果遇到了就繼續(xù)往下一層搜索,如果沒遇到,就立即返回。

?

?

例題分析

LeetCode 第?212?題:給定一個(gè)二維網(wǎng)格?board?和一個(gè)字典中的單詞列表 words,找出所有同時(shí)在二維網(wǎng)格和字典中出現(xiàn)的單詞。

? ? ? ?

單詞必須按照字母順序,通過相鄰的單元格內(nèi)的字母構(gòu)成,其中“相鄰”單元格是那些水平相鄰或垂直相鄰的單元格。同一個(gè)單元格內(nèi)的字母在一個(gè)單詞中不允許被重復(fù)使用。

?

說明:你可以假設(shè)所有輸入都由小寫字母?a-z?組成。

?

?

解題思路

這是一道出現(xiàn)較為頻繁的難題,題目給出了一個(gè)二維的字符矩陣,然后還給出了一個(gè)字典,現(xiàn)在要求在這個(gè)字符矩陣中找到出現(xiàn)在字典里的單詞。

?

由于字符矩陣的每個(gè)點(diǎn)都能作為一個(gè)字符串的開頭,所以必須得嘗試從矩陣中的所有字符出發(fā),上下左右一步步地走,然后去和字典進(jìn)行匹配,如果發(fā)現(xiàn)那些經(jīng)過的字符能組成字典里的單詞,就把它記錄下來。

?

可以借用深度優(yōu)先的算法來實(shí)現(xiàn)(關(guān)于深度優(yōu)先算法,將在第 06 節(jié)課深入探討),如果你對(duì)它不熟悉,可以把它想象成走迷宮。

字典匹配的解法 1:每次都循環(huán)遍歷字典,看看是否存在字典里面,如果把輸入的字典變?yōu)楣<系脑?#xff0c;似乎只需要?O(1)?的時(shí)間就能完成匹配。

?

但是,這樣并不能進(jìn)行前綴的對(duì)比,即,必須每次都要進(jìn)行一次全面的深度優(yōu)先搜索,或者搜索的長度為字典里最長的字符串長度,這樣還是不夠高效。

?

字典匹配的解法 2:對(duì)比字符串的前綴,借助前綴樹來重新構(gòu)建字典。

?

假如在矩陣?yán)镉龅搅艘粋€(gè)字符”V”,而字典里根本就沒有以“V”開頭的字符串,則不需要將深度優(yōu)先搜索進(jìn)行下去,可以大大地提高搜索效率。

?

構(gòu)建好了前綴樹之后,每次從矩陣?yán)锏哪硞€(gè)字符出發(fā)進(jìn)行搜索的時(shí)候,同步地對(duì)前綴樹進(jìn)行對(duì)比,如果發(fā)現(xiàn)字符一直能被找到,就繼續(xù)進(jìn)行下去,一步一步地匹配,直到在前綴樹里發(fā)現(xiàn)一個(gè)完整的字符串,把它輸出即可。

?

線段樹(Segment Tree)

舉例:假設(shè)有一個(gè)數(shù)組?array[0 … n-1], 里面有?n?個(gè)元素,現(xiàn)在要經(jīng)常對(duì)這個(gè)數(shù)組做兩件事。

  1. 更新數(shù)組元素的數(shù)值

  2. 求數(shù)組任意一段區(qū)間里元素的總和(或者平均值)

    ?

?

?

解法 1:遍歷一遍數(shù)組。

  • 時(shí)間復(fù)雜度?O(n)。

?

解法 2:線段樹。

  • 線段樹,就是一種按照二叉樹的形式存儲(chǔ)數(shù)據(jù)的結(jié)構(gòu),每個(gè)節(jié)點(diǎn)保存的都是數(shù)組里某一段的總和。

  • 適用于數(shù)據(jù)很多,而且需要頻繁更新并求和的操作。

  • 時(shí)間復(fù)雜度 O(logn)。

?

實(shí)現(xiàn)

舉例:數(shù)組是?[1, 3, 5, 7, 9,?11],那么它的線段樹如下。

?

? ? ? ?

根節(jié)點(diǎn)保存的是從下標(biāo)?0?到下標(biāo)?5?的所有元素的總和,即?36。左右兩個(gè)子節(jié)點(diǎn)分別保存左右兩半元素的總和。按照這樣的邏輯不斷地切分下去,最終的葉子節(jié)點(diǎn)保存的就是每個(gè)元素的數(shù)值。

?

解法

1. 更新數(shù)組里某個(gè)元素的數(shù)值

從線段樹的根節(jié)點(diǎn)出發(fā),更新節(jié)點(diǎn)的數(shù)值,它保存的是數(shù)組元素的總和。修改的元素有可能會(huì)落在線段樹里一些區(qū)間里,至少葉子節(jié)點(diǎn)是肯定需要更新的,所以,要做的是從根節(jié)點(diǎn)往下,判斷元素的下標(biāo)是否在左邊還是右邊,然后更新分支里的節(jié)點(diǎn)大小。因此,復(fù)雜度就是遍歷樹的高度,即?O(logn)。

?

2. 對(duì)數(shù)組某個(gè)區(qū)間段里的元素進(jìn)行求和

方法和更新操作類似,首先從根節(jié)點(diǎn)出發(fā),判斷所求的區(qū)間是否落在節(jié)點(diǎn)所代表的區(qū)間中。如果所要求的區(qū)間完全包含了節(jié)點(diǎn)所代表的區(qū)間,那么就得加上該節(jié)點(diǎn)的數(shù)值,意味著該節(jié)點(diǎn)所記錄的區(qū)間總和只是所要求解總和的一部分。接下來,不斷地往下尋找其他的子區(qū)間,最終得出所要求的總和。

?

建議:線段樹的實(shí)現(xiàn)書寫起來有些繁瑣,需要不斷地練習(xí)。

?

例題分析

?

LeetCode 第?315?題:給定一個(gè)整數(shù)數(shù)組 nums,按要求返回一個(gè)新數(shù)組 counts,使得數(shù)組 counts 有該性質(zhì)——counts[i] 的值是 nums[i] 右側(cè)小于?nums[i] 的元素的數(shù)量。

?

示例

輸入:[5, 2, 6, 1]

輸出:[2, 1, 1, 0]?

?

解釋

5 的右側(cè)有 2 個(gè)更小的元素(2 和 1)

2 的右側(cè)僅有 1 個(gè)更小的元素(1)

6 的右側(cè)有 1 個(gè)更小的元素(1)

1 的右側(cè)有 0 個(gè)更小的元素

?

解題思路

給定一個(gè)數(shù)組?nums,里面都是一些整數(shù),現(xiàn)在要求打印輸出一個(gè)新的數(shù)組?counts,counts?數(shù)組的每個(gè)元素?counts[i]?表示?nums?中第?i?個(gè)元素右邊有多少個(gè)數(shù)小于?nums[i]。

?

例如,輸入數(shù)組是?[5, 2,?6, 1],應(yīng)該輸出的結(jié)果是?[2, 1, 1, 0]。

?

因?yàn)?#xff0c;對(duì)于?5,右邊有兩個(gè)數(shù)比它小,分別是?2?和?1,所以輸出的結(jié)果中,第一個(gè)元素是?2;對(duì)于?2,右邊只有?1?比它小,所以第二個(gè)元素是?1,類推。

?

如果使用線段樹解法,需要理清線段樹的每個(gè)節(jié)點(diǎn)應(yīng)該需要包含什么樣的信息。

?

線段樹每個(gè)節(jié)點(diǎn)記錄的區(qū)間是數(shù)組下標(biāo)所形成的區(qū)間,然而對(duì)于這道題,因?yàn)橐y(tǒng)計(jì)的是比某個(gè)數(shù)還要小的數(shù)的總和,如果把分段的區(qū)間設(shè)計(jì)成按照數(shù)值的大小來劃分,并記錄下在這個(gè)區(qū)間中的數(shù)的總和,就能快速地知道比當(dāng)前數(shù)還要小的數(shù)有多少個(gè)。

? ? ? ?? ? ? ?

1. 首先,讓從線段樹的根節(jié)點(diǎn)開始,根節(jié)點(diǎn)記錄的是數(shù)組里最小值到最大值之間的所有元素的總和,然后分割根節(jié)點(diǎn)成左區(qū)間和右區(qū)間,不斷地分割下去。

2. 初始化,每個(gè)節(jié)點(diǎn)記錄的在此區(qū)間內(nèi)的元素?cái)?shù)量是?0,接下來從數(shù)組的最后一位開始往前遍歷,每次遍歷,判斷這個(gè)數(shù)落在哪個(gè)區(qū)間,那么那個(gè)區(qū)間的數(shù)量加一。

3. 遇到?1,把它加入到線段樹里,此時(shí)線段樹里各個(gè)節(jié)點(diǎn)所統(tǒng)計(jì)的數(shù)量會(huì)發(fā)生變化。

4. 當(dāng)前所遇到的最小值就是?1。

5.?把?6?加入到線段樹里。

6. 求比?6?小的數(shù)有多少個(gè),即查詢線段樹,從?1?到?5?之間有多少個(gè)數(shù)。

7. 從根節(jié)點(diǎn)開始查詢。由于所要查詢的區(qū)間是?1?到?5,無法包含根節(jié)點(diǎn)的區(qū)間?1?到?6,所以繼續(xù)往下查詢。

8. 左邊,區(qū)間?1?到?3?被完全包含在?1?到?5?之間,把該節(jié)點(diǎn)所統(tǒng)計(jì)好的數(shù)返回。

9. 右邊,區(qū)間?1?到?5?跟區(qū)間?4?到?6?有交叉,繼續(xù)往下看,區(qū)間?4?到?5?完全被包含在?1?到?5?之間,所以可以馬上返回,并把統(tǒng)計(jì)的數(shù)量相加。

10. 最后得出,在當(dāng)前位置,在?6?的右邊比?6?小的數(shù)只有一個(gè)。

?

通過這樣的方法,每次把當(dāng)前的數(shù)用線段樹進(jìn)行個(gè)數(shù)統(tǒng)計(jì),然后再計(jì)算出比它小的數(shù)即可。算法復(fù)雜度是?O(nlogn)。

?

樹狀數(shù)組(Fenwick Tree / Binary Indexed Tree)

?

實(shí)現(xiàn)

舉例:假設(shè)有一個(gè)數(shù)組?array[0 … n-1], 里面有?n?個(gè)元素,現(xiàn)在要經(jīng)常對(duì)這個(gè)數(shù)組做兩件事。

  1. 更新數(shù)組元素的數(shù)值

  2. 求數(shù)組前?k?個(gè)元素的總和(或者平均值)

?

解法 1:線段樹。

  • 線段樹能在?O(logn)?的時(shí)間里更新和求解前?k?個(gè)元素的總和。

?

解法 2:樹狀數(shù)組。

  • 該問題只要求求解前 k 個(gè)元素的總和,并不要求任意一個(gè)區(qū)間。

  • 樹狀數(shù)組可以在?O(logn)?的時(shí)間里完成上述的操作。

  • 相對(duì)于線段樹的實(shí)現(xiàn),樹狀數(shù)組顯得更簡單。

?

特點(diǎn)

樹狀數(shù)組的數(shù)據(jù)結(jié)構(gòu)有以下幾個(gè)重要的基本特征。

  1. 它是利用數(shù)組來表示多叉樹的結(jié)構(gòu),在這一點(diǎn)上和優(yōu)先隊(duì)列有些類似,只不過,優(yōu)先隊(duì)列是用數(shù)組來表示完全二叉樹,而樹狀數(shù)組是多叉樹。

  2. 樹狀數(shù)組的第一個(gè)元素是空節(jié)點(diǎn)。

  3. 如果節(jié)點(diǎn)?tree[y]?是?tree[x]?的父節(jié)點(diǎn),那么需要滿足條件:y =?x?- (x & (-x))。

?

建議:由于樹狀數(shù)組所解決的問題跟線段樹有些類似,所以不花篇幅進(jìn)行問題的討論。LeetCode?上有很多經(jīng)典的題目可以用樹狀數(shù)組來解決,比如?LeetCode?第?308?題,求一個(gè)動(dòng)態(tài)變化的二維矩陣?yán)?#xff0c;任意子矩陣?yán)锏臄?shù)的總和。

?

總結(jié)

這節(jié)課講解了一些高級(jí)的數(shù)據(jù)結(jié)構(gòu)。

?

1. 優(yōu)先隊(duì)列

經(jīng)常出現(xiàn)在考題里的,它的實(shí)現(xiàn)過程比較繁瑣,但是很多編程語言里都有它的實(shí)現(xiàn),所以在解決面試中的問題時(shí),實(shí)行“拿來主義”即可。

鼓勵(lì)你自己練習(xí)實(shí)現(xiàn)一個(gè)優(yōu)先隊(duì)列,在實(shí)現(xiàn)它的過程中更好地去了解它的結(jié)構(gòu)和特點(diǎn)。

?

2. 圖

被廣泛運(yùn)用的數(shù)據(jù)結(jié)構(gòu),很多涉及大數(shù)據(jù)的問題都得運(yùn)用到圖論的知識(shí)。

比如在社交網(wǎng)絡(luò)里,每個(gè)人可以用圖的頂點(diǎn)表示,人與人直接的關(guān)系可以用圖的邊表示;再比如,在地圖上,要求解從起始點(diǎn)到目的地,如何行駛會(huì)更快捷,需要運(yùn)用圖論里的最短路徑算法。

?

3. 前綴樹

出現(xiàn)在許多面試的難題當(dāng)中。

因?yàn)楹芏鄷r(shí)候你得自己實(shí)現(xiàn)一棵前綴樹,所以你要能熟練地書寫它的實(shí)現(xiàn)以及運(yùn)用它。

?

4. 線段樹和樹狀數(shù)組

應(yīng)用場合比較明確。

例如,問題變?yōu)樵谝环鶊D片當(dāng)中修改像素的顏色,然后求解任意矩形區(qū)間的灰度平均值,那么可以考慮采用二維的線段樹了。

?

建議:LeetCode 平臺(tái)上,針對(duì)上面的這些高級(jí)數(shù)據(jù)結(jié)構(gòu)都有豐富的題目,希望你能用功學(xué)習(xí)。

總結(jié)

以上是生活随笔為你收集整理的高级数据结构讲解与案例分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。