數據結構與算法

[toc]

范圍

優先級隊列,二分搜索,滑動窗口,雙指針,單調棧
不會考動規了,貪心和BFS/DFS我考這么多次也沒遇上過,主要集中在字符串、二分法、滑窗/雙指針、二叉樹上

LC上沒啥設計題,推薦在3ms上搜過往的真題自己練習??傊褪嵌嘤肙O,多建幾個對象,不要用一堆List、Map存,不然到時候query、process、remove的時候要同時更新好幾個集合,很痛苦還容易出錯。

數據結構&算法


一、總體目標

功能上,實現CRUD,其中,查找是其他的基礎;性能上,考慮時間復雜度和空間復雜度(內存和外存)。

排序問題還是查找問題,數據是靜態還是動態的,數據有無重復的

指針&引用,都是指內存的地址

1.時間復雜度

平均時間復雜度就是加權平均期望時間復雜度,分析的時候要結合概率論的知識。

2.要學習它的由來、特性、適用的場景以及它能解決的問題。

工程常用:紅黑樹、跳表

3.性能升級

鏈表改成平衡二叉查找樹

紅黑樹、跳表、有序動態數組還是散列表呢?

時空互換(查表,hashmap存儲,緩存中間的計算結果)

- 海量數據小內存的處理

1.小內存,分流到不同的服務器上,分批處理的思路,借助臨時的文件,處理使用散列表+排序等等實現一個文件,多個文件的處理結果再處理下。

2.硬盤,數據庫存儲

3.分治思想

- 大數值范圍的數據處理,溢出


二、數據結構

1.數組&鏈表

數組,優秀的是隨機訪問(已知下標),O(1),針對查找一個數,綜合來講,還是依次遍歷比較,還是O(n)

鏈表,單鏈表,循環環鏈表(頭尾),雙向鏈表(前后鄰居都知道)

  • 鏈表是否帶一個哨兵結點(一個占位的空節點),避免插入刪除對頭尾結點的特別處理

    實現LRU緩存淘汰算法,維護一個按照訪問時間從大到小有序排列的鏈表結構

跳表,鏈表+多級索引(hash),見下文,跳表中存儲的數據本來就是有序的了

java初始化二維數組的三種方式
char[][] num={{'1','2','3'},{'3','5'}};
列表倒序
Collections.reverse(list);

2.棧&隊列

棧(Stack),先進后出,左右括號一一匹配性,運算符計算

隊列(Queue),先進先出,資源排隊,循環隊列,阻塞隊列,并發隊列(基于數組的循環隊列,利用CAS原子操作,可以實現非常高效的并發隊列)

Deque

Deques can also be used as LIFO (Last-In-First-Out) stacks. deque(雙端隊列)

PriorityQueue

LinkedList

LinkedList本質是雙向鏈表,是可以當做隊列或棧使用的。Queue q=new LinkedList();

  • push pop(棧)
  • addFirst、addLast、removeFirst、removeLast(隊列)
  • add/offer,remove/poll,peek取而不刪,功能一致,前者拋異常

3.MAP

LinkedHashpMap

=>accssprder =true.天然的LRU

4.二叉樹

可以數組存儲(2i和2i+1是子節點的下標,根存在1)適合完全二叉樹緊密排布;可以鏈表(常用)。

二叉搜索樹

  • 紅黑樹
二叉樹前中后序遍歷
preOrderTraverse(TreeNode root){
if root ==null 
    return;
print(root);//根
preOrder(root.left);//左
preOrder(root.right);//右
}

inOrderTraverse(TreeNode root){
if root ==null 
    return;
inOrder(root.left);//左
print(root);//根
inOrder(root.right);//右
}

postOrderTraverse(TreeNode root){
if root ==null 
    return;
postOrder(root.left);//左
postOrder(root.right);//右
print(root);//根
}

N叉樹遍歷
Traverse(TreeNode root){
if root ==null 
    return;
前序:print(root);//根
for child in root.childs:
    preOrder(child);//子節點
}
后序:print(root);//根

    注意:樹的遍歷,根是在循環的外面;與回溯不同

5.圖

頂點&邊(度)。無向圖,度:一個頂點有多個條邊(微信好友);有向圖,入度&出度(微博好友關系);帶權圖。

存儲方法:

  • 鄰接矩陣,如果頂點i與頂點j之間有邊,我們就將A[i][j]和A[j][i]標記為1,對于帶權圖,數組中就存儲相應的權重。清晰但容易稀疏圖容易浪費空間。
  • 鄰接表,每個頂點的鏈表中存儲的,是跟這個頂點有邊相連的頂點。注意,有向圖有出入的,需要多個實現。

三、要解決的問題

1.排序

選擇影響因素:數組是否基本有序、時間復雜度(最好最差平均)、原地操作、是否能穩定(相同值的順序排序后不變)

[圖片上傳失敗...(image-6136eb-1692357029183)]
$$
O(n^2),冒泡、插入、選擇排序,時間復雜度 ,空間復雜度 原地操作,不需要額外申請空間,

優先插入排序(選擇不穩定、冒泡每次交換要賦值4個,還需要輔助變量)
$$

冒泡

提前剪枝:當前一輪比較沒有要交換的,說明已經有序,可以break了

√插入=》希爾排序

數組分為兩部分,前面有序的,后面待排序的。從a[1]開始,將當前這個數插入到合適的位置。

涉及到數組插入元素

選擇

數組分為兩部分,每次待排序中選擇最小的放到有序的下一個(與待排序的第一個交換)
O(nlogn),歸并、快速排序,時間復雜度,分治思想

歸并

分治思想,分為兩個子規模,處理它有序后+合并兩個有序數組(需要輔助函數)。遞歸來實現。

雖然幾乎任意O(nlogn),但是合并費內存,使用得少。

合并多個有序的結構:我們從這100個文件中,各取第一個字符串,放入數組中,然后比較大小,把最小的那個字符串放入合并后的大文件中,并從數組中刪除。我們將從小文件中取出來的字符串放入到小頂堆(插入、刪頂O(logn))中,那堆頂的元素,也就是優先級隊列隊首的元素,就是最小的字符串。

√快速排序

快排核心思想就是分治和分區。partition函數(原地交換(選擇排序思想,數組O(1)插入))選擇一個分界點,將比它小的放它左邊,將比它大的放它右邊,(此時不用管左邊各個元素有序),

[圖片上傳失敗...(image-60e4e4-1692357029183)]

  • 如何在O(n)的時間復雜度內查找一個無序數組中的第K大元素?

    ——分區思想,保證左分區A[0,p-1]<privot,A[p]<右分區A[p+1,r], 比較K與p+1的大小,可以確定 大致的哪個分區,再進行遞歸

都是遞歸實現。歸并先分子問題,再合并,快排先確定分界,再分子問題。歸并非原地操作,穩定;快排是原地的,但不穩定。但是考慮到分區點的選擇,快排的時間復雜度就從O(nlogn)退化成了O(n2)=》合理選擇分區點(左右分區差不多),隨機法;三數取中法(每隔xx個數取一個數,再取平均值,頭中尾)

堆排序

堆排序包含兩個過程,建堆和排序。我們將下標從?22n到11的節點,依次進行從上到下的堆化操作,然后就可以將數組中的數據組織成堆這種數據結構。接下來,我們迭代地將堆頂的元素放到堆的末尾,并將堆的大小減一,然后再堆化,重復這個過程,直到堆中只剩下一個元素,整個數組中的數據就都有序排列了。

非常穩定,原地操作。性能略遜于快排(1.一個順序訪問,一個跳著訪問;2.堆排序的交換次數多)

Glibc.qsort:輸入數據量下,使用歸并排序;否則使用快速排序,此過程中,如果區間數量《4,使用快排排這部分數(在小規模數據面前,O(n2)時間復雜度的算法并不一定比O(nlogn)的算法執行時間長)。

防止遞歸溢出,自己實現一個堆上的棧,手動模擬遞歸來解決的。
O(n),線性排序:桶排序、計數排序、基數排序,不涉及比較(O(n))

桶排序

11000數值內的所有數放到桶01,100012000數值內的所有數放到桶02……,每個桶內部使用快排。最后按桶的順序組合起來。

適合外部排序的情況,十萬個數,100M內存,分批排序。

分到不同的桶里,不需要比較?——hashmap等,O(1)

存放在不同的桶里,這里不需要內存嗎?——可以寫入到磁盤文件,每滿就寫個文件

如何確定數據范圍、及大致的分布?

計數排序

特殊的桶排序。桶數=數據范圍個數,那么可以統計出每個值的分布個數C[],再累加這個數組,得到小于等于這個數值的數據的個數(也是排序后的下標)。掃描原始數組,取出C[元素]=排序后數組的下標,給新數組賦值后然后再C[元素]--=下一個相同數的下標。

此時,需要申請的內存比較大(1:1)

計數排序只能用在數據范圍不大的場景中,如果數據范圍k比要排序的數據n大很多,就不適合用計數排序了。而且,計數排序只能給非負整數排序,如果要排序的數據是其他類型的,要將其在不改變相對大小的情況下,轉化為非負整數。

基數排序

10萬個11位電話號碼,從低位整體一位位地比較,10萬個數比較,用上面的O(n)穩定的排序算法,進行比較。

當字符串不等長時,可以使用'0'補齊。

API

`Stream.sorted`、`Collections.sort`、`List.sort`和`Arrays.sort`方法  +  比較器
Collections.reverse(news);
比較器:Comparator<Student> nameComp = (s1, s2) -> s1.getName().compareTo(s2.getName());
                                =Comparator.comparing(Student::getName, 可選(s1, s2) -> s2.compareTo(s1));
                                = Comparator.reversed()
list.sort(Comparator.comparingInt(String::length).reversed()
                .thenComparing(Comparator.comparing(String::toLowerCase, Comparator.reverseOrder())));

取前10個=》堆

時間有序性
特性有序性,動態維護數據有序性,「優先隊列」(堆)是可以勝任的
PriorityQueue的迭代遍歷不是有序的,而是二叉樹的保存結構
LinkedList,頭插addFirst


2.查找

二分

數據集合得有序。內存空間連續,數據規模適中的場景,也不能太大。

二分查找只能用在插入、刪除操作不頻繁,一次排序多次查找的場景中。針對動態變化的數據集合,二分查找將不再適用。

二分查找更適合用在“近似”查找問題,在這類問題上,二分查找的優勢更加明顯。比如今天講的這幾種變體問題,用其他數據結構,比如散列表、二叉樹,就比較難實現了。

[圖片上傳失敗...(image-e5bcd3-1692357029183)]

鏈表實現二分,鏈表+多級索引=》跳表,Redis中的有序集合。動態數據結構,可以支持快速地插入、刪除、查找操作,寫起來也不復雜,甚至可以替代紅黑樹。多級索引,內存消耗大??臻g換時間。實際開發中,比較的對象,我們只是索引要比較的數據,比起對象來,內存是可以忽略的。如果一直插入的話,索引之間的間距就會失衡,極端情況某一截特別長,退化成鏈表=》隨機函數,隨機函數生成了值K,那我們就將這個結點添加到第一級到第K級這K級索引中。

[圖片上傳失敗...(image-e87c1a-1692357029183)]

按照區間來查找數據這個操作,紅黑樹的效率沒有跳表高。代碼可讀性實現簡單??梢詣討B重構節點。

針對動態數據集合=》二叉樹???

http://3ms.huawei.com/km/blogs/details/10428257

散列Hash

利用了數組的隨機讀取的特性,實現近似“O(1)”的時間復雜度(沖突)。編號Key和數組下標的映射方法=》散列函數,f(key)=index,∈N,散列函數生成的值要盡可能隨機并且均勻分布*。例如,Word文檔中單詞拼寫檢查功能

如何避免hash沖突(不同的key,可能通過hash函數指向同一個下標)。再好的散列函數也無法避免散列沖突。散列表碰撞攻擊:引起嚴重的性能下降,比如十萬倍,導致服務器無法響應其他請求,實現DoS攻擊。

  • 裝載因子:設置冗余數組空間,降低沖突的概率??梢灶愃芕ector,進行動態的擴容。區別是此處,可以’慢慢‘搬移:如果對排列順序沒有明顯的要求,可以在每次插入新數據的時候,搬移一個老的數據到新的空間中,避免一次性搬移耗時大。查找的時候,就查兩處。散列就是如此的。

  • 開放尋址:idx=hash(key)+i,i∈N,依次往后查找可用空間插入;查找的時候,比較是否相等直到遇到空閑的下標;刪除,需要將這個位置標記為deleted(避免查找的時候斷鏈子)

    • 二次探測:idx=hash(key)+i平方,i∈N
    • 雙重散列:有好多個散列函數,一個不行就換另一個
  • 鏈表法:每個數組下標跟一個鏈表(或其他更高效的紅黑樹等等,JKD1.8的HashMap,8內用鏈表,8外用紅黑樹),執行鏈表的插入刪除查找

LinkedHashMap:天然支持LRU算法,按訪問順序排列。實現是靠雙向鏈表和散列表

哈希算法

實際上,散列函數也是哈希算法的一種應用。

任意長度的二進制值串映射為固定長度的二進制值。從哈希值不能反向推導出原始數據(所以哈希算法也叫單向哈希算法)。分別是安全加密、唯一標識(信息摘要,如何快速判斷圖片是否在圖庫中)、數據校驗完整性和正確性、散列函數、分布式系統(一個機器的性能不夠,通過hash將任務有規律得分配到xx號機器上):負載均衡、數據分片、分布式存儲。

針對常用的密碼,大量重復出現在,是可以預先計算出來的,那就可以拿到密碼了。怎么辦呢?字典攻擊,我們可以引入一個鹽(salt),跟用戶的密碼組合在一起,增加密碼的復雜度

二叉查找樹/二叉有序樹

子樹<根<=右子樹,重復數據插入到根的右子樹

中序遍歷二叉查找樹,可以輸出有序的數據序列,時間復雜度是O(n),非常高效。因此,二叉查找樹也叫作二叉排序樹。

  • 平衡二叉查找樹,左右子樹的高度差不大,樹的高度接近logn,插入、刪除、查找O(logn),平衡二叉查找樹在某些方面還是優于散列表,解決普通二叉查找樹在頻繁的插入、刪除等動態更新的情況下,出現時間復雜度退化的問題。
    • AVL樹,AVL樹是一種高度平衡的二叉樹,所以查找的效率非常高,但是,有利就有弊,AVL樹為了維持這種高度的平衡,就要付出更多的代價。每次插入、刪除都要做調整,就比較復雜、耗時。所以,對于有頻繁的插入、刪除操作的數據集合,使用AVL樹的代價就有點高了。
    • Splay Tree(伸展樹)、Treap(樹堆)
    • 紅黑樹,不嚴格的平衡二叉查找樹

其實就是一種完全二叉樹,每個節點的值必現大于等于子樹。最常用的存儲方式就是數組。

堆頂元素存儲的就是堆中數據的最大值或者最小值。相比于二叉查找樹,弱化了左右子樹間的大小約束,強化了二叉樹的歸左特性。

插入,從下往上堆化(插到最后子節點的位置,依次與父節點比較大小直至滿足大小關系;刪除堆頂,將最后一個葉子節點放到堆頂,再從下往上堆化。避免了空洞,不滿足完全二叉樹的特性。刪除堆頂數據和往堆中插入數據的時間復雜度都是O(logn)。

堆的應用一:優先級隊列

按優先級高的先出。比如,赫夫曼編碼、圖的最短路徑、最小生成樹算法等等。Java的PriorityQueue。歸并排序的,合并多個有序數組。

堆的應用二:利用堆求Top K

我們可以維護一個大小為K的小頂堆,插入的時候,與堆頂比較,滿足就更新。最后,返回這K個東西。

堆的應用三:利用堆求中位數,求xx位的數據

靜態的數據結構,直接排序去中間下標;

動態數據結構,使用一個大頂堆和一個小頂堆,大頂堆中存儲前半部分數據,小頂堆中存儲后半部分數據,且小頂堆中的數據都大于大頂堆中的數據。插入,如果新加入的數據小于等于大頂堆的堆頂元素,我們就將這個新數據插入到大頂堆;否則,我們就將這個新數據插入到小頂堆。插入后,看下個數是否只相差1,否則移動一個

圖的搜索算法

BFS

按層搜索,O(頂點數+邊數)。從s頂點到t頂點,bfs找到的就是最短路徑??臻g復雜度是O(頂點數*3),記錄visited數組,當前層queue隊列,結果路徑存儲的數組。
6度空間交友規則。

DFS

走分岔路,一條道直到黑,不撞南墻不回頭(回頭的條件,下一個頂點已經查過了)。從s頂點到t頂點,dfs找到的不一定是最短路徑。時間O(邊數*2),空間同上。

回溯思想,遞歸實現,需要一個全局found標志位來中斷遞歸。

連通圖(圖中的所有頂點都是連通的)

A、IDA

字符串匹配

單模式串匹配

BF暴力樸素搜索

雙重循環,O(n*m)

Rabin-Karp算法

主串按模式串的長度切分查找,通過哈希算法對主串中的n-m+1個子串分別求哈希值,然后逐個與模式串的哈希值比較大小。

哈希算法的設計:將26字母轉換成26進制(位權值26),將m位字符串位計算出十進制的數值。那么,右移一位的結果值跟上一次的結果值及兩次差異的兩個字符是有遞推公式的。=》通過一次主串的掃描,就可以計算出所有待匹配串的哈希值。

Boyer-Moore算法

怎么樣不匹配的時候多滑動幾位?從模式串的末尾比起,

KMP算法

多模式串匹配(多個模式串和一個主串之間做匹配)

Trie樹字典樹

利用字符串之間的公共前綴,將重復的前綴合并在一棵樹下,形成差異N叉數。如果有新的要插入了,是怎么擴展樹的每一層的呢?二叉樹的實現,是使用了pleft和pright。但是這里,分支數不是固定的,所以使用一個LinkedList?

實現搜索引擎的搜索關鍵字提示,IDE自動輸入補全。此時,分支數有范圍,0~26,可以使用一個數組來映射下一級的,這樣插入也很方便,查找這里O(1)。顯然,這種方式控件耗費打,且浪費。再考慮到工程實現,

Trie樹比較適合的是查找前綴匹配的字符串。精確匹配查找,在工程中,更傾向于用散列表或者紅黑樹。

敏感詞過濾,上面的多個模式串之間是有關聯的(有共同的前置),敏感詞可沒有關聯???

拼寫糾錯功能呢?量化兩個字符串的相似度(編輯距離指的就是,將一個字符串轉化成另一個字符串,需要的最少編輯操作次數(比如增加一個字符、刪除一個字符、替換一個字符))。

208 純純Trie樹(有可能已經插入了,先判空)
211 Tire樹+BFS+DFS

AC自動機

借鑒KMP算法對BF算法進行改進(對Tire樹構建失敗指針),對Tire樹進行多往后滑動幾位。


四、算法思想

1.遞歸

核心3步:分解成小規模的問題(可能是幾個,一次可以跨1級2級臺階),考慮問題之間的遞推關系,確定最小規模的終止條件。

代碼簡潔,但時間復雜度高。實現依賴系統的棧,每次都要保存現場,內存要求高。

避免重復計算,使用一個數據結構保存中間的結果,每次都先查詢有無以計算好的結果

警惕stackOverFlow=》尾遞歸?

如果避免環遞歸

2.貪心

霍夫曼編碼(Huffman Coding)、Prim和Kruskal最小生成樹算法、還有Dijkstra單源最短路徑算法。

第一步,當我們看到這類問題的時候,首先要聯想到貪心算法:針對一組數據,我們定義了限制值和期望值,希望從中選出幾個數據,在滿足限制值的情況下,期望值最大。

第二步,我們嘗試看下這個問題是否可以用貪心算法解決:每次選擇當前情況下,在對限制值同等貢獻量的情況下,對期望值貢獻最大的數據。如果每一次的選擇之間是獨立的沒有約束的,那么就可以。如果前面的選擇會影響后面的選擇,那么不能用。

第三步,我們舉幾個例子看下貪心算法產生的結果是否是最優的。

貪心即使顯而易見最好的原則,又有可能不是最優的解。

物品可分割背包問題,可以使用貪心。

3.分治算法

將原問題劃分成n個規模較小,并且結構與原問題相似的子問題,子問題之間獨立(與動態規劃的區別),遞歸地解決這些子問題,然后再合并其結果,就得到原問題的解。分治算法是一種處理問題的思想,遞歸是一種編程技巧。

歸并排序的實現。MapReduce的本質就是分治,集群并行處理。MapReduce框架只是一個任務調度器,底層依賴GFS來存儲數據,依賴Borg管理機器。它從GFS中拿數據,交給Borg中的機器執行,并且時刻監控機器執行的進度,一旦出現機器宕機、進度卡殼等,就重新從Borg中調度一臺機器執行。

4.回溯算法

深度優先搜索、八皇后、0-1背包問題、圖的著色、旅行商問題、數獨、全排列、正則表達式匹配、編譯原理中的語法分析,八皇后問題。適合用用遞歸來實現。

0-1背包問題模型(物品是不可分割的,要么裝要么不裝,無法貪心),我們可以把物品依次排列,整個問題就分解為了n個階段,每個階段對應一個物品怎么選擇。先對第一個物品進行處理,選擇裝進去或者不裝進去,然后再遞歸地處理剩下的物品。

正則表達式,

搜索剪枝

回溯算法和 DFS 算法的細微差別是:回溯算法是在遍歷「樹枝」,DFS 算法是在遍歷「節點」

做選擇,繼續走,撤銷選擇再循環;全局變量結果集results,終止條件要深拷貝singleResult。
List<List<Integer>> results=new LinkedList<>();
solution(int[] nums){
    List<integer> singleResult=new LinkedList<>();
    backTrack(nums,singleResult);
}
backtrack(選擇列表,路徑){
if 終止條件滿足:
    result.add(路徑深拷貝)//results.add(new LinkedList(singleResult));
    return;
    
for 選擇 in 選擇列表:
    # 做選擇
    將該選擇從選擇列表移除
    路徑.add(選擇)
        
    backtrack(路徑, 選擇列表)
        
    # 撤銷選擇
    路徑.remove(選擇)
    將該選擇再加入選擇列表
}

[圖片上傳失敗...(image-eee80d-1692357029183)]

全排列,選項數字數組
八皇后,選項一行的所有列的數字,校驗布局二維數組,放置位置是一維
解數獨,選項1-9數組,校驗布局二維數組,放置位置是二維(先列后行回溯)

5.動態規劃

求解最優問題。一個模型三個特征:多階段決策最優解模型,經歷多個決策階段。每個決策階段都對應著一組狀態。最優子結構、無后效性重復子問題。

0-1背包重量問題(物品不可分割),遍歷所有物品,每次循環,當前物品可以取,也可以不取,記錄每一輪所有可能的結果的值(重復的結果合并)。(每次在上一輪的結果上,+0,或+當前物品的值。) f(i)=f(i-1)+a[i]/0.

0-1背包問題,引入物品價值,數組中記錄物品的價值。

  • 狀態轉移表法回溯算法實現-定義狀態-畫遞歸樹-找重復子問題-畫狀態轉移表-根據遞推關系填表-將填表過程翻譯成代碼。我們先畫出一個狀態表。狀態表一般都是二維的,所以你可以把它想象成二維數組。其中,每個狀態包含三個變量,行、列、數組值。我們根據決策的先后過程,從前往后,根據遞推關系,分階段填充狀態表中的每個狀態。對于(i, j)重復的節點,我們只需要選擇dist最小的節點,繼續遞歸求解,其他節點就可以舍棄了。最后,我們將這個遞推填表的過程,翻譯成代碼,就是動態規劃代碼了。
  • 狀態轉移方程找最優子結構-寫狀態轉移方程-將狀態轉移方程翻譯成代碼

303 一維數據[left,right]求和

6.四大算法比較

貪心、回溯、動態規劃可以歸為一類多階段決策最優解模型,,分治不是。

回溯算法是個“萬金油”。基本上能用的動態規劃、貪心解決的問題,我們都可以用回溯算法解決。回溯算法相當于窮舉搜索。 一般能用動態規劃解決的問題,都可以使用回溯算法的暴力搜索解決。區別,是否存在重復可合并的子問題。有重復的話,1.回溯加“備忘錄”,2.動規狀態轉移表法 。

能用動態規劃解決的問題,需要滿足三個特征,最優子結構、無后效性和重復子問題。分治算法要求分割成的子問題,不能有重復子問題,而動態規劃正好相反,動態規劃之所以高效,就是因為回溯算法實現中存在大量的重復子問題。

貪心算法實際上是動態規劃算法的一種特殊情況。它解決問題起來更加高效,代碼實現也更加簡潔。不過,它可以解決的問題也更加有限。它能解決的問題需要滿足三個條件,最優子結構、無后效性和貪心選擇性(這里我們不怎么強調重復子問題,通過局部最優的選擇,能產生全局的最優選擇)。

多階段決策最優解模型”:

我們一般是用動態規劃來解決最優問題。而解決問題的過程,需要經歷多個決策階段。每個決策階段都對應著一組狀態。然后我們尋找一組決策序列,經過這組決策序列,能夠產生最終期望求解的最優值。

要注意“階段”是指的是什么,是每一選項選擇,還是數目/步數。

最優子結構、無后效性重復子問題

1.最優子結構

最優子結構指的是,問題的最優解包含子問題的最優解。反過來說就是,我們可以通過子問題的最優解,推導出問題的最優解。如果我們把最優子結構,對應到我們前面定義的動態規劃問題模型上,那我們也可以理解為,后面階段的狀態可以通過前面階段的狀態推導出來。

2.無后效性

無后效性有兩層含義,第一層含義是,在推導后面階段的狀態的時候,我們只關心前面階段的狀態值,不關心這個狀態是怎么一步一步推導出來的。第二層含義是,某階段狀態一旦確定,就不受之后階段的決策影響。無后效性是一個非常“寬松”的要求。只要滿足前面提到的動態規劃問題模型,其實基本上都會滿足無后效性。

3.重復子問題

這個概念比較好理解。前面一節,我已經多次提過。如果用一句話概括一下,那就是,不同的決策序列,到達某個相同的階段時,可能會產生重復的狀態。

背包問題

1.物品可分割——貪心

2.物品不可分割——動規

找零錢問題

1.一元錢10張,二元錢5張,五元錢3張,一共付12元,最少多少張——貪心

2.一元錢N張,二元錢N張,五元錢N張,一共付12元,最少多少張——動規

路徑/迷宮/二維數組,得到一個結果——動規


刷題

二分

[low,high]
while (left <= right)
low=mid+1;
high=mid-1;

69. x 的平方根

  • 右側區間

278. 第一個錯誤的版本

  • 左側區間

34. 在排序數組中查找元素的第一個和最后一個位置

  • 左側區間+右側區間

611. 有效三角形的個數

  • 兩邊之和大于第三邊
  • 排序后,兩條邊二重循環,第三條邊可以二分查找,后面的都滿足

169. 多數元素

  • 一道簡單題,5種解法,方法五:Boyer-Moore 投票算法
  • 可以分治遞歸
  • 次數哈希

215. 數組中的第K個最大元素

  • 堆PriorityQueue
  • TODO學會手動建堆

合并區間/線段

  • 左端點升序,右端點降序

56. 合并區間

57. 插入區間(合并區間)

  • TODO 56&57的關系

1288. 刪除被覆蓋區間

  • 左端點升序,右端點降序
  • 因為左端點升序,不需要二重循環,一旦不滿足,立刻以這個為基準,一遍掃描即可

435. 無重疊區間

  • 貪心:先定右端點最小的區間,再找更小的左端點,右端點排序
  • 動態規劃:以當前區間i為最后一個區間,可以選出的區間數量的最大值。二重循環,超時
  • 類似 預定最多的會議=》最先結束的會議,排序,才能排更多的會議
  • TODO 300. 最長遞增子序列

雙指針

75. 顏色分類

  • 雙指針標記3段前后的分段下標(經典的在線算法。適用于不知道數據量有多大,但用戶一次只給你一個數。從任意時刻來看,數組都是有序的)
  • 數個數重新構建數組(不講武德)

451. 根據字符出現頻率排序

  • 頻次哈希,重建StringBuilder
  • HashMap排序?
  • PriorityQueue,優于直接map排序
  • 使用int[26+26+10][2],[key][cnt]來存儲頻次,Arrays.sort二重排序
    -TODO 桶排序)

滑動窗

209. 長度最小的子數組

  • 二重循環,超時,因為重復計算i,i+1,i+2
  • 滑動窗口
  • 前綴和(避免區間和重復計算)+二分:正數組,前綴和是遞增的,查找sum[j]>=target+sum[i]的最小j-i+1
    • 此處二分的類似611. 有效三角形的個數,有很多備選,不一定非要挨個判斷是否滿足=>逆向:當有序的時候,可以考慮使用二分折半查找

567. 字符串的排列

438. 找到字符串中所有字母異位詞

  • 與567相同的題目,只是一個是找到即return,一個記錄所有下標再返回。

TODO 1438. 絕對差不超過限制的最長連續子數組

前綴樹

知識點:N叉樹;子樹,字母時往往使用int[26],可以用HashMap
使用場景:瀏覽器提示聯想,輸入糾錯
前綴樹小結

208. 實現 Trie (前綴樹)

211. 添加與搜索單詞 - 數據結構設計

  • Trie + dfs搜索(.通配,需要遞歸到底才能知道是否匹配)

TODo 648. 單詞替換

TODo 677. 鍵值映射

TODo 676. 實現一個魔法字典

TODO 各類「區間和」問題策略

前綴和

  • 知識點:重復計算,可以緩存結果啊
  • 簡單的動態規劃,類似斐波那契數列

303. 區域和檢索 - 數組不可變

304. 二維區域和檢索 - 矩陣不可變

  • 二維看面積,(A-B)(C-D)=AC-AD-BC+BD。

差分數組

  • 知識點:對差分數組做前綴和,得到原數組。差分<=>前綴和,互為逆操作。(求導和積分)
  • 前綴和,不需要多一個來構建+1。原題是帶0的,正常使用,否則起止下標都要減一。
  • 提示:區間增減
  • 小而美的算法技巧:差分數組 | labuladong 的算法小抄

1109. 航班預訂統計

1094. 拼車

TODO 798. 得分最高的最小輪調

給定數組A,任意連續區間[Ai,Aj]中,尋找最大Ai-Aj值。

  • a2-a4= ( a2-a3)+ (a3-a4)。 緩存每次的差值,依次相加,遇0重新起點,比較最大的差值。一遍掃描。
int numCodeStats = codeStats.size(), ma = 0, Value = 0;
        for(int i = 1; i < numCodeStats; i++){
            Value = codeStats[i-1] - codeStats[i] + Value;
            Value = max(0, Value);
            ma = max(ma,  Value);
        }
        return ma;

線段樹

單調棧

  • 單調棧實際上就是棧,只是利用了一些巧妙的邏輯,使得每次新元素入棧后,棧內的元素都保持有序(單調遞增或單調遞減)。
  • 處理「下一個更大元素」,「上一個更小元素」問題
  • labuladong
    496. 下一個更大元素 I

739. 每日溫度

503. 下一個更大元素 II

  • 有些要回頭查=》把數組復制一遍+單調棧,循環數組通用處理:%。如果一直失敗,考慮是不是漏了=號,此時也要pop
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容