1. 程式人生 > 其它 >【演算法】常見資料結構基本演算法整理

【演算法】常見資料結構基本演算法整理

技術標籤:面試java

【演算法】常見資料結構基本演算法整理

置頂 夏洛的網 2018-03-17 16:33:41 2029 收藏 12
分類專欄: 演算法 資料結構
版權
去年11月份聽了牛客網的課,當時做了紙質的筆記整理。

現在為了以後方便查詢,將問題目錄整理如下。

每道題只提供解題思路,不貼原始碼。

可能會稍微手寫一下程式碼(沒有在IDE上測,為了試下以後面試時手寫程式碼),或者虛擬碼。

by 03/17/2018

其實本科學過資料結構、演算法設計,而且後來也看過不止一次,但這次又聽左神講,真的是“每次都有新發現”,覺得很有趣,必須要記錄一下。

該篇部落格中某些演算法的具體講解可以去部落格:

http://blog.csdn.net/liuxiao214/article/details/78649765 中檢視。

小目錄
雖然博有自帶目錄,我還是想把所有內容都列出來。

1、時間複雜度計算

2、氣泡排序、利用位運算交換元素

2.1 利用位運算交換

2.2 氣泡排序

3、插入排序

4、選擇排序

5、隨機快排

6、二分查詢

7、Master公式

8、歸併排序

9、利用歸併排序求小和

10、利用歸併排序求逆序對

11、堆排序建堆調整 堆

12、桶排序

13、比較器的使用

14、計數排序

15、排序後最大相鄰數差值問題

16、KMP演算法

17、KMP演算法應用最大重合的新串

18、KMP演算法應用兩個二叉樹是否子樹匹配

19、Manacher演算法及其擴充套件新增新串使迴文且最短

19.1 Manacher找出字串str最大的迴文子串

19.2 給定str1在其後新增字元使得新字串是迴文字串且最短

20、BFPRT演算法求第K小大的數

21、基數排序

22、希爾排序

23、實現特殊棧,O(1)實現getmin()

24、用陣列結構實現大小固定的佇列和棧

25、用棧實現佇列、用佇列實現棧

26、貓狗佇列

27、雜湊表和雜湊函式

28、設計RandomPool結構

29、轉圈列印矩陣、轉90度列印矩陣

30、之字型列印矩陣

31、在行列有序的矩陣中找數

32、列印兩個有序連結串列的公共部分

33、判斷一個連結串列是否是迴文結構

34、將單向連結串列按照某值劃分為左邊小、中間相等、右邊大結構,且內部保持原有順序

35、複製含有隨機指標節點的連結串列

36、兩個單鏈表相交的一系列問題

37、反轉單向、雙向連結串列

38、隨時找到資料流的中位數

39、金條切塊

40、求收益問題

41、摺紙問題

42、二叉樹的前序、中序、後序遍歷的遞迴與非遞迴實現

43、較為直觀的列印二叉樹

44、在二叉樹中找到一個節點的後繼節點

45、並查集

46、布隆過濾器

47、二分搜尋

48、字首樹及其六個應用

48.1 找B陣列中哪些串是A陣列中串的字首

48.2 查詢某個字串是否在陣列中

48.3 查詢某個字串出現否,且出現幾次

48.4 二叉樹曾經有多少個路徑經過當前節點

48.5 刪除字串

48.6 整型陣列中都是3位數,是否能得到最趨近999的和

49、圖、無向圖、有向圖、鄰接表、鄰接矩陣

50、圖的寬度優先遍歷

51、圖的深度優先遍歷

52、拓撲排序

53、最小生成樹:Kruskal演算法、Prim演算法

54、Dijkstra演算法

55、遞迴概念

56、漢諾塔問題

57、列印一個字串的全部子序列

58、列印一個字串的全部序列

59、母牛生小牛問題、人走臺階問題

60、給定數字字串,轉換成字母字串

61、逆序棧,不用額外空間,遞迴

62、動態規劃概念

63、陣列從左上走到右下的最小路徑和

64、陣列中的數是否能累加到aim

65、01揹包問題

66、堆的應用、攻克城市

67、陣列代表容器,求max裝水量

68、最大的leftmax與rightmax之差的絕對值

69、求最大子陣列和

70、字串是否可迴圈右移得到某字串

71、字串右移K位後的字串,原地調整

72、生成視窗最大值陣列

73、拼接字串,使其字典順序最小

74、佔用會議室問題

75、Morris遍歷

76、搜尋二叉樹

77、平衡樹AVL

78、SB樹、紅黑樹

79、跳錶

一、第一次課(2017.11.11)
1、時間複雜度計算
描述一個流程中常數運算元的指標。只看高階項,忽略常數係數。

2、氣泡排序、利用位運算交換元素
2.1 利用位運算交換
已知:a^a=0 a^0=a

void swap(int &a ,int &b)
{
a = a^b; //
b = a^b; //b=abb=a
a = a^b: //a=aba=b
}
1
2
3
4
5
6
注意,這種操作要保證兩個數是不同地址的數,否則會發生自己跟自己異或結果為0的情況。

2.2 氣泡排序
每次都首項開始依次向後比較,將大的數向後傳送,找到最大的那個,放到末尾。第二次時找到倒數第二大的數。

時間:O(N^2)
空間:O(1)
可做到穩定性

void bubble_sort(vector&a)
{
for(int i=a.size()-1;i>0;i–)
{
for(int j=0;j<i;j++)
{
if(a[j+1]>a[j])
swap(a[j+1],a[j]);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
3、插入排序
類似摸牌,從陣列第二個位置開始,依次向前面已經排好序的陣列中插入。

時間:O(N^2)
空間:O(1)
可做到穩定性

void insert_sort(vector&a)
{
for(int i=1;i<a.size();i++)
{
for(int j=i;j>0;j–)
{
if(a[j]<a[j-1])
swap(a[j],a[j-1]);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
4、選擇排序
從第一個位置開始,從後開始找到最小的那個,放在第一個位置;
繼續從第二個位置往後找第二小,放第二個位置;
…….

時間:O(N^2)
空間:O(1)
不可做到穩定性

void select_sort(vector&a)
{
for(int i=0;i<a.size()-1;i++)
{
int min = i;
for(int j=i+1;j<a.size();j++)
{
if(a[j]<a[min])
min=j;
}
swap(a[i],a[min]);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
5、隨機快排
時間:O(NlogN)
空間:O(logN)
常規實現做不到穩定性,有論文實現,不必考慮

利用遞迴,每次隨機選取一個值,將其作為基準值,小於它的放左邊,等於的放中間,大於的放右邊,然後遞迴呼叫左右兩邊。

vector partition(vectora, int l, int r)
{
int less = l-1;
int more = r;
while(l<more)
{
if(a[l]<a[r])
swap(a[++less], a[l++]);
else if(a[l]>a[r])
swap(a[–more], a[l]);
else
l++;
}
swap(a[r],a[more]);
vectorp;
p.push_back(less+1);
p.push_back(more);
return p;
}

void quick(vectora, int l, int r)
{
if(l>=r)
return;
int pindex = rand()%(r-l+1);
swap(a[l + pindex],a[r]);
vectorp = partition(a, l, r);
quick(a, l, a[p[0]-1]);
quick(a, a[p[1]+1], r);
}

void quick_sort(vectora)
{
if (a.size()<2)
return ;
quick(a, 0, a.size()-1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
6、二分查詢
在已經排好序的陣列中查詢某個數是否存在。一般是取中間值比較,等於則存在,大於則遞迴呼叫右邊,小於則遞迴呼叫左邊。

bool binary(vectora, int x)
{
int l = 0;
int r = a.size()-1;
while(l<r)
{
int mid = l + (r-l)>>1;
if (a[mid]==x)
return true;
if (a[mid] > x)
l = mid + 1;
if (a[mid] < x)
r = mid - 1;
}
return false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
7、Master公式
對於遞迴行為的複雜度,可以用公式來解決。如果一個N規模的程式可以分為N/b個規模,一共做a次(a次遞迴,每次N/b次操作),或者還帶有其他非遞迴的複雜度,如下:

T(N)=aT(Nb)+O(Nd)
如果logba>d,則T(N)=O(Nlogba)
如果logba<d,則T(N)=O(Nd)
如果logba=d,則T(N)=O(NdlogN)
二、第二次課(2017.11.12)
8、歸併排序
遞迴劃分兩組,直到不能再分,組內有序,兩兩合併排序,需要額外陣列。

時間:O(NlogN)
空間:O(N)

void merge(vector&a, int l, int mid, int r)
{
vectorhelp(r-l+1);
int p1 = l;
int p2 = mid + 1;
int i = 0;
while(p1<=mid && p2<=r)
help[i++] = a[p1]<a[p2]?a[p1++]:a[p2++];
while(p1<=mid)
help[i++] = a[p1++];
while(p2<=r)
help[i++] = a[p2++];
for(i = 0;i<help.size();i++)
a[l+i] = help[i];
}
void merge_sort(vector&a, int l, int r)
{
if(l == r)
return;
int mid = l + (r-l)>>1;
merge_sort(a, l, mid);
merge_sort(a, mid+1, r);
merge(a, l, mid, r);
}
merge_sort(a, 0, a.size()-1);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
9、利用歸併排序求小和
小和就是某個數之前所有比它小的數的和。求小和就是求整個陣列的小和之和。

如:3 5 1 4 6,其小和就是3+(3+1)+(3+5+1+4)=20

利用歸併排序,每次兩兩合併時,如果左邊組一個數比右邊組一個數小,那他肯定比右邊組那個數及其後面的數都小。

用O(NlogN)解決。

int merge(vector&a, int l, int mid, int r)
{
vectorhelp(r-l+1);
int p1 = l;
int p2 = mid + 1;
int i = 0;
int sum = 0;
while(p1<=mid && p2<=r)
sum += a[p1]<a[p2]?(a[p1]*(l-p2+1)):0;
help[i++] = a[p1]<a[p2]?a[p1++]:a[p2++];
while(p1<=mid)
help[i++] = a[p1++];
while(p2<=r)
help[i++] = a[p2++];
for(i = 0;i<help.size();i++)
a[l+i] = help[i];
return sum;
}
int merge_sort(vector&a, int l, int r)
{
if(l == r)
return;
int mid = l + (r-l)>>1;
return merge_sort(a, l, mid)+merge_sort(a, mid+1, r)+merge(a, l, mid, r);
}
merge_sort(a, 0, a.size()-1);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
10、利用歸併排序求逆序對
逆序對是一個數,前面有比他大的數,這兩個數就構成了逆序對。求陣列中有多少個逆序對。用O(NlogN)解決。

利用歸併排序,當兩個組在合併時,如果左邊組的一個數比右邊組的一個數大,那左邊組這個數及其後面的數都會比右邊組的這個數大,這些數就構成了逆序對。

int merge(vector&a, int l, int mid, int r)
{
vectorhelp(r-l+1);
int p1 = l;
int p2 = mid + 1;
int i = 0;
int sum = 0;
while(p1<=mid && p2<=r)
sum += a[p1]>a[p2]?(mid-p1+1):0;
help[i++] = a[p1]>a[p2]?a[p2++]:a[p1++];
while(p1<=mid)
help[i++] = a[p1++];
while(p2<=r)
help[i++] = a[p2++];
for(i = 0;i<help.size();i++)
a[l+i] = help[i];
return sum;
}
int merge_sort(vector&a, int l, int r)
{
if(l == r)
return;
int mid = l + (r-l)>>1;
return merge_sort(a, l, mid)+merge_sort(a, mid+1, r)+merge(a, l, mid, r);
}
merge_sort(a, 0, a.size()-1);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
11、堆排序、建堆、調整 堆
演算法上的堆是一個完全二叉樹,已知節點i,其父節點為(i-1)/2,左孩子為2i+1,右孩子為2i+2。

使用大根堆,任何一個節點,都是其子樹中值最大的點。

堆排序,就是把整個陣列變成一個大根堆,然後將根節點與尾節點交換,再向下調整大根堆。

如何建立大根堆:將節點依次插入堆中,如果碰到比自己的父節點大,則與其交換,遞歸向上調整。

根節點與尾節點交換後如何調整大根堆:向下調整,與左右孩子比較,若大於他們,不需要調整,如果小於,則跟其中大的孩子交換。然後遞歸向下調整。

void heapinsert(vector&a, int index)
{
while(a[index]>a[(index-1)/2])
{
swap(a[index],a[(index-1)/2]);
index = (index-1)/2;
}
}
void heapify(vector&a, int index, int size)
{
int left = 2index+1;
while(left<size)
{
int largest = ((left+1<size) && (a[left+1]>a[left]))?left+1:left;
largest = a[largest]>a[index]?largest:index;
if (largest==index)
break;
swap(a[largest], a[index]);
index = largest;
left = 2
index+1;
}
}
void heap_sort(vector&a)
{
if(a.size()<2)
return;
for(int i=1;i<a.size();i++)
heapinsert(a, i);
int size = a.size();
swap(a[0],a[–size]);
while(size>0)
{
heapify(a, 0, size);
swap(a[0],a[–size]);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
12、桶排序
分為計數排序和基數排序。分開說。

這種不基於比較的排序,可實現時間複雜度為O(N),但浪費空間,對資料的位數與範圍有限制。

13、比較器的使用
使用系統的預設排序函式sort時,是從小到大的,你可以改變比較器實現不同方式的排序。

bool mycompare1(int a, int b)
{//降序
return a - b > 0;
}
// 注意不能加等於號!
bool mycompare2(int a, int b)
{//升序
return b - a > 0;
}
sort(A.begin(), B.end(), mycomapre1);
1
2
3
4
5
6
7
8
9
10
14、計數排序
根據陣列中的最大值max準備max+1個桶,每個桶記錄陣列中的這個數出現的次數。然後根據桶中的數字一次倒出每個數即可。

void bucket_sort(vector&a)
{
if(a.size()<2)
return;
int max = INT_MIN;
for (int i=0;i<a.size();i++)
max = a[i]>max?a[i];max;
vectorbucket(max+1,0);
for(int i=0;i<a.size();i++)
bucket[a[i]]++;
int j=0;
for(int i=0;i<bucket.size();i++)
{
while(bucket[i]–>0)
a[j++] = i;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
15、排序後最大相鄰數差值問題
即n個數,則準備n+1個桶,最後一個桶放最大值max,其他min~(max-1)的數在前n個桶桑平分。比如說數i,它放在哪個桶中就取決於:((i-min)/(max-min))*n。

如果說有一個桶為空,則其左邊第一個不為空的桶的最大值a,與其右邊第一個不為空的桶的最小值b,這兩個數在排序後一定是相鄰的,且他們的差值比每個桶內的差值都大。所以只需要記錄這些每個桶內的max和min,比較相鄰的前後非空桶的min-max與桶內的max-min的差值大小即可。

所以準備三個n+1大小的陣列,一個數組記錄這個桶是否為空,另外兩個陣列分別記錄這個桶內的max和min。

int maxgap(vectora)
{
if(a.size()<2)
return 0;
int imax = INT_MIN;
int imin = INT_MAX;
for(int i=0;i<a.size();i++)
{
imax = a[i]>imax?a[i]:imax;
imin = a[i]<imin?a[i]:imin;
}
if(imin==imax)
return 0;
vectorisempty(a.size()+1);
vectoramax(a.size()+1);
vectoramin(a.size()+1);
int index=0;
for(int i=0;i<a.size();i++)
{
index = (a[i]-imin)* a.size()/(imax-imin);
amax[index] = amax[index]?max(amax[index], a[i]):a[i];
amin[index] = amin[index]?min(amin[index],a[i]):a[i];
isempty[index] = true;
}
int gap = isempty[0]?amax[0]-amin[0]:0;
int lastmax = amax[0];
for (int i=1;i<isemppty.size();i++)
{
if(isempty[i])
{
gap = max(gap, amin[i]-lastmax);
lastmax = amax[i];
}
}
return gap;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
16、KMP演算法
想看詳細介紹,請參見部落格:
http://blog.csdn.net/liuxiao214/article/details/78026473

字串匹配問題,字串p是否是字串s的子串,是的話,返回p在s中的起始位置,否的話返回-1。

暴力的方法是p與s的第一個字元比較,相等就繼續比較,不相等就換s的第二個字元繼續比較,這樣做的話,遍歷s的指標i會發生回溯,導致時間複雜度為O(N^2)。

那最好是i指標不用發生回溯。這裡就用到了字串的最大公共字首字尾。

首先是找字串p的最大公共字首字尾,比如ABCAB,其最大公共字首字尾就是AB,長度是2。這樣當我們發生不匹配時,直接按照其最大公共字首字尾長度移動字串p就可以。

當然我們不直接使用最大相同公共字首字尾,我們使用的是next陣列,這個陣列中的數是不包含當前字元的最大字首字尾的長度,如圖:

這裡寫圖片描述

然後這樣字串匹配時,如果發生不匹配,遍歷字串s的指標i不必回溯,只需移動字串p即可,即更改遍歷p的指標j,j變為其next陣列中的值。

求next陣列利用遞迴。直接上程式碼:

vector getnext(string s)
{
vectornext(s.length());
next[0] = -1;
if (s.length()<2)
return next;
next[1] = 0;
int cn = 0;
int i = 2;
while(i<s.length())
{
if(s[i-1]s[cn])
next[i++] = ++cn;
else if(cn>0)
cn = next[cn];
else
next[i++] = 0;
}
}
int kmp(string s, string p)
{
if(s.length()<p.length() || s.length
0 || p.length0)
return -1;
int i = 0;
int j = 0;
vectornext=getnext§;
while(i<s.length() && j<p.length())
{
if(s[i]p[j])
{
i++;
j++;
}
else if(next[j]
-1)
i++;
else
j = next[j];
}
return j
p.length()?i-j:-1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
三、第三次課(2017.11.18)
17、KMP演算法應用:最大重合的新串
給定一個字串str1,只能往str1的後面新增字元變成str2。

要求:str2中必須包含兩個str1,即兩個str1可以有重合,但不能是同一位置開頭,即完全重合;且str2儘量短。

利用next陣列,找到str1的最大公共字首字尾,然後向str1尾部新增從字首後開始到str1原尾部的字元。

如:str1=abracadabra,則str2=abracadabra+cadabra

int getnext(string s)
{
vectornext(s.length()+1);
next[0]=-1;
if (s.length()<2)
retrun -1;
next[1] = 0;
int cn = 0;
int i = 2;
while(i<s.length()+1)
{
if(s[i-1]==s[cn])
next[i++] = ++cn;
else if(cn>0)
cn = next[cn];
else
next[i++] = -1;
}
return next[s.length()];
}
string gettwo(string s)
{
if (s.length()==0)
return “”;
if (s.length()==1)
return s+s;
if (s.length()==2)
return s[0]==s[1]?s+s.substr(1):s+s;
return s + s.substr(getnext(s));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
18、KMP演算法應用:兩個二叉樹是否子樹匹配
給定兩個二叉樹T1,T2,返回T1的某個子樹結構是否與T2相等。

常規解法是遍歷頭節點,看其子樹結構是否與T2相等。

這裡將二叉樹T1與T2都轉換為字串,是否匹配就看T2是否是T1的子串。

如何轉換呢?每一個節點的值後面都加一個特殊符號,比如“_”,空節點用另一個特殊符號表示,如“#”。序列化要按照同樣的遍歷方式,比如前序遍歷。

 3

1 2
2 3
1
2
3
上圖中序列化後為“3 _ 1 _ 2 _ # _ #_ 3 _ # _ # _ 2 _ # _ # _”

struct TreeNode
{
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x):val(x),left(NULL),right(NULL){}
};

string tree2string(TreeNode *root)
{
if(rootNULL)
return “#";
string str = to_string(root->val) + "
”;
return str + tree2string(root->left) + tree2string(root->right);
}
vector getnext(string s)
{
vectornext(s.length());
next[0] = -1;
if (s.length()<2)
return s;
next[1] = 0;
int cn = 0;
int i = 2;
while(i<s.length())
{
if(s[i-1]
[cn])
next[i++] = ++cn;
else if (cn>0)
cn = next[cn];
else
next[i++] = -1;
}
return next;
}
int kmp(string s, string p)
{
if(s.length() < p.length() || s.length()==0 || p.length()==0)
return -1;
vectornext = getnext§;
int i = 0;
int j = 0;
while(i<s.length() && j<p.length())
{
if(s[i] == p[j])
{
i++;
j++;
}
else if (next[j]>0)
j = next[j];
else
i++:
}
return j == p.length() ? i-j : -1;
}
bool issubtree(TreeNode *root1, TreeNode *root2)
{
string s = tree2string(root1);
string p = tree2string(root2);
return kmp(s, p) != -1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
19、Manacher演算法及其擴充套件(新增新串使迴文且最短)
19.1 Manacher:找出字串str最大的迴文子串
首先解決奇迴文和偶迴文的問題。

我們在判斷字串是否迴文時,是根據一個字元(奇迴文)或空(偶迴文)來判斷的,這樣需要分情況討論,比較麻煩,所以最好統一一下。

新增輔助字元,(隨便哪個字元都可以,不會影響原有字元匹配就可以),如“#”,在字元與字元之間、開頭和結尾各新增“#”,這個“#”就相當於一個虛軸,虛軸只會和虛軸匹配。這樣就可以統一奇迴文和偶迴文了。

傳統方法是從str的每個字元開始,向兩邊擴,找到最大回文子串,複雜度為O(N^2)。

首先是將字串轉換為新增特殊字元後的新字串。然後進行查詢最大回文子串。

首先規定幾個概念:

迴文最右邊界R
迴文最右邊界R的迴文中心C
迴文半徑陣列radius,記錄每個字元以其為中心的迴文半徑。
最大回文半徑與最大回文半徑中心。
一共分為四種情況討論。

1、當遍歷到i時,如果字元i在最右邊界R的右邊,只能採用暴力匹配方式,向兩邊擴。
2、如果i在最右邊界R的左邊,則找到i關於R的對稱點i’,如果i’的迴文左邊界在R的迴文邊界中,則i的迴文邊界也在R的迴文邊界中,不會再向外擴。
3、如果i’的迴文左邊界不在R的迴文邊界中,則i的迴文右邊界剛好與R重合,也不會擴。
4、如果i’的迴文左邊界剛好在R的迴文左邊界上,則i的迴文半徑至少到了R,至於R之後會不會擴,只能暴力匹配了。

那麼在上述四種情況下,2、3的迴文邊界就不用暴力匹配了,直接去radiux[2C-i](即i’的迴文半徑)與R-i的小值就可以了。

string get_newstring(stirng s)
{
string ss = “#”;
for (int i=0;i<s.length();i++)
ss = ss + s[i] + “#”;
return ss;
}
string manacher(string s)
{
if(s.length()<2)
return “”;
string news = get_newstring(s);
vectorradius(news.length(), 0);
int C = -1;
int R = -1;
int rmax = INT_MIN;
int rc = 0;
for(int i=0; i<news.length(); i++)
{
radius[i] = 1;
radius[i] = R>i ? min(R-i, radius[2*C-i]) : radius[i];
while( i - radius[i] >= 0 && i + radius[i] < news.length())
{
if(news[i - radius[i]] == news[i + radius[i]])
radius[i]++;
else
break;
}
if(i+raidus[i]>R)
{
R = i + radius[i];
C = i;
}
if(radius[i]>rmax)
{
rmax = radius[i];
rc = i;
}
}
rmax–;
return s.substr((ic-imax)/2, imax);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
19.2 給定str1,在其後新增字元使得新字串是迴文字串且最短
找到包含最後一個字元的最大回文子串,然後將這個迴文子串之前的字元反向貼在str1的後面即可。

如“abc12321”,即將“abc”反向貼在後面即可,“abc12321cba”

string get_newstring(string s)
{
string news = “#”;
for(int i=0; i<s.length(); i++)
news = news + s[i] + “#”;
return news;
}
int manacher(string s)
{
string news = get_newstring(s);
vectorradius(news.length());
int C = -1;
int R = -1;
for(int i=0; i<news.length(); i++)
{
radius[i] = R>i ? min(R-i, radius[2*C-i]) : 1;
while(i - radius[i] >=0 && i + radius[i] < news.length())
{
if(news[i-radius[i]] == news[i+radius[i]])
radius[i]++;
}
if(i+radius[i]>R)
{
R = i+ radius[i];
C = i;
if (R == news.length())
return radius[i]-1;//包含最後一個字元的最大回文子串的長度
}
}
}
string getmaxstring(string s)
{
int r = manacher(s);
string ss = s;
for(int i= s.length()-r-1; i>=0; i–)
ss = ss + s[i];
return ss;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
20、BFPRT演算法:求第K小/大的數
利用快排中的partition過程。如果求第K小,就是求排序後陣列中下標為k-1的值。如果k-1屬於partition後的等於區,則可直接返回等於區的數,如果,在大於區,則遞迴呼叫大於區,在小於區,則遞迴呼叫小於區。

到那時BFPRT又做了優化,即在選取partition的劃分值時不再是隨機選取,而是有策略的選取。

如何選呢?首先,將陣列5個一組進行劃分,不足5個自動一組。

然後分別組內排序(可用插入排序),求出每個組內的中位數,將這些中位陣列成新的陣列mediumarray。

然後遞迴呼叫這個BFPRT的函式,得到這個中位數陣列的上中位數。這個值就是進行partition的劃分值。

vector partition(vector&a, int l, int r, int value)
{
int less = l-1;
int more = r+1;
while(l<more)
{
if(a[l]<value)
a[++less]=a[l++};
else if (a[l]>value)
a[–more]=a[l];
else
l++;
}
vectorp;
p.push_back(less+1);
p.push_back(more-1);
}

void insert_sort(vector&a, int start, int end)
{
if(start==end)
return ;
for(int i=start+1;i<=end;i++)
{
for(int j=i-1;j>=start;j–)
{
if(a[j+1]<a[j])
swap(a[j+1],a[j]);
}
}
}

int getmedium(vector&a, int start, int end)
{
insert_sort(a, start, end);
return a[strat + (end-start)/2];
}

int get_medium_of_medium(vector&a, int start, int end)
{
int num = end-start+1;
int flag = num%5 == 0 ? 0 : 1;
vectormediums(num/5+flag);
for(int i=0; i< mediums.size(0; i++)
{
int istart = start + i*5;
int iend = istart + 4;
mediums[i] = getmedium(a, istart, min(iend, end));
}
return findk(mediums, 0, mediums.size()-1, (mediums.size()-1)/2);
}

int findk(vector&a, int start, int end, int k)
{
if(start==end)
return a[strat];
int pvalue = get_medium_of_medium(a, start, end);
vectorp = partition(a, start, end, pvalue);
if(k>=p[0] && k<=p[1])
return pvalue;
else if(k<p[0])
return findk(a, start, p[0]-1, k);
else
returun findk(a, p[1]+1, end, k);
}

int mink(vectora, int k)
{
if(k<1 || k>a.size())
return NULL;
return findk(a, 0, a.size()-1, k-1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
21、基數排序
找到最大數的位數,並準備10個桶(0-9)。從個位數到高位數依次排序,按照其位上的數的大小依次入桶,順序要保持一致,保持先進先出。

int maxbit(vectora)
{
int imax = INT_MIN;;
for(int i=0; i<a.size() && imax<a[i]; i++)
imax = a[i];
int digit = 0;
while(imax>0)
{
imax /= 10;
digit++;
}
return digit;
}

int getnum(int x, int digit)
{
return (x/(pow(10, digit-1))) % 10;
}

void radix(vector&a, int start, int end, int digit)
{
vectorhelp(end-start+1);
int num;
for(int d=1; d<=digit; d++)
{
vectorcount(10,0);
for(int i=0; i<a.size(); i++)
{
num = getnum(a[i], d);
count[num]++;
}
fot(int i=1; i<10; i++)
count[i] += count[i-1];
for(int i=end; i>=start; i–)
{
num = getnum(a[i], d);
help[count[num]-1] = a[i];
count[num]–;
}
for(int i=0; i<help.size(); i++)
a[start + i]=help[i];
}
}

void radix_sort(vectora)
{
if(a.size()<2)
return ;
radix(a, 0 ,a.size()-1, maxbits(a));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
22、希爾排序
設定步長,每次選一個步長來進行排序,做很多遍,最終落在步長為1的排序上。大步長調整完基本有序,則小步長不必調整過多。

插入排序就是步長為1的希爾排序。

12 3 1 9 4 8 2 6 (未排序)
選擇步長為4
4 3 1 6 12 8 2 9
選擇步長為2
1 3 2 6 4 8 12 9
選擇步長為1
1 2 3 4 6 8 9 12
1
2
3
4
5
6
7
23、實現特殊棧,O(1)實現getmin()
進棧、出棧、返回棧頂這些都與原來的棧的操作保持一致,主要是加了怎樣在O(1)時間下得到棧內最小值。

兩種方法,不過都要準備兩個棧,一個數據棧data存放資料,一個help棧,存放最小值。

方法一:help棧存放最小值,每次data棧壓棧時,help也壓棧,不過help要保持最小的棧頂,即如果data新進的數如果比help棧頂小,則這個數可以壓棧入help,否則將help棧頂的那個數再一次壓入help棧中。

壓棧的時候同步壓,出棧同步出。

方法二:當壓入data棧的數小於等於help棧頂時,可以向help壓入,否則不壓。當要彈出data棧的數時,如果這個數與help的棧頂值相等,則help也彈出,否則help不彈出。

則help的棧頂一直是當前棧內最小的數。

24、用陣列結構實現大小固定的佇列和棧
24.1 實現棧
維護一個size(初始為0),代表棧的大小,當push時,數直接放在a[size]上,size++,當pop時,直接取a[size-1]]的值,size–。

struct array_stack
{
vectorastack;
int size;
array_stack(int initsize)
{
if(initsize<0)
cout<<“請輸入一個大於0的數,以保證棧不為空”<<endl;
astack=vector(initsize);
size = 0;
}
int top()
{
if(size0)
{
cout<<“the stack is empty”<<endl;
return NULL;
}
return a[size-1];
}
void push(int x)
{
if(size
astack.size())
{
cout<<“the stack is full”<<endl;
return ;
}
astack[size++] = x;
}
int pop()
{
if(size==0)
{
cout<<“the stack is empty”<<endl;
return NULL;
}
return astack[–size];
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
24.2 實現佇列
維護start、end、size三個指標,size表示佇列的大小(約束start與end的關係)。當push時,end++,pop時,start++,用start去追end,誰先觸底,則回到開始位置。

struct array_queue
{
vectoraqueue;
int size;
int start;
int end;
aqueue(int initsize)
{
if(initsize<0)
cout<<“請輸入一個大於0的數,以保證佇列不為空”<<endl;
aqueue = vector(initsize);
size = 0;
start = 0;
end = 0;
}
int top()
{
if(size == 0)
return null;
return aqueue[start];
}
void push(int x)
{
if(size == aqueue.size())
{
cout<<“the queue is full”<<endl;
return ;
}
aqueue[end] = x;
size ++;
end = end==aqueu.size()-1?0:end+1;
}
int pop()
{
if(size == 0)
{
cout<<“the queue is empty”<<endl;
return NULL;
}
size–;
int temp = start;
start = start == aqueue.size()-1?0:start+1;
return aqueue[temp];
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
25、用棧實現佇列、用佇列實現棧
25.1 用棧實現佇列
準備兩個棧,一個數據棧data,一個輔助棧help,資料棧負責入佇列,如果需要出佇列,就把資料棧中的當前所有資料倒入輔助棧help中。然後從help中出佇列。

注意兩點:如果help棧不為空,data棧不要向help棧倒資料;data棧倒資料時,一次要全倒完。

這裡寫圖片描述

struct stack2queue
{
stackdata;
stackhelp;
stack2queue()
{
data = new stack();
help = new stack();
}
int top()
{
if(data.empty() && help.empty())
{
cout<<“the queue is empty”<<endl;
return NULL;
}
else if(help.empty())
{
while(!data.empty())
help.push(data.pop());
}
return help.top();
}
void push(int x)
{
data.push(x);
}
int pop()
{
if(data.empty() && help.empty())
{
cout<<“the queue is empty”<<endl;
return NULL;
}
else if(help.empty())
{
while(!data.empty())
help.push(data.pop());
}
return help.pop();
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
25.2 用佇列實現棧
準備兩個佇列A和B,兩個佇列互相配合,稱為資料佇列和快取佇列。當進棧時,向資料佇列中入,當出棧時,將目前的那個資料棧除最後一個元素外全部出佇列,並進入到快取佇列,此時資料佇列只剩那個要出棧的元素,可出。

此資料佇列變為空,為快取佇列,快取佇列為資料佇列。

struct queue2stack
{
queuedata;
queuehelp;
queue2stack()
{
data = new queue();
help = new queue();
}
int top()
{
if(data.empty())
{
cout<<“the stack is empty”<<endl;
return NULL;
}
while(data.size()!=1)
help.push(data.pop());
int temp = data.pop();
help.push(temp);
swap();
return temp;
}
void push(int x)
{
data.push(x);
}
int pop()
{
if(data.empty())
{
cout<<“the stack is empty”<<endl;
return NULL;
}
while(data.size()!=1)
help.push(data.pop());
int temp = data.pop();
swap();
return temp;
}
void swap()
{
queuetemp = data;
data = help;
help = data;
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
26、貓狗佇列
有三個類,寵物類、貓類、狗類。

實現一種貓狗佇列結構,要求:

1、使用者可以呼叫add方法將cat類或dog類的例項放入佇列中。
2、pollall函式:按照先進先出原則,依次彈出所有例項。
3、polldag函式:先進先出彈出dog例項。
4、pollcat函式:先進先出彈出cat例項。
5、isempty函式:檢查佇列中是否還有例項。
6、isdogempty函式:檢查佇列中是否還有dog結構。
7、iscatempty函式:檢查佇列中是否還有cat結構。

方法:維護一個index,用來記錄每個類是第幾個進入佇列的,在poll哪個類的index小,就先poll誰。

四、第四次課(2017.11.19)
27、雜湊表和雜湊函式
雜湊函式:

輸入域是無窮大的,輸出域是有限的。
當輸入引數一旦確定,引數固定,則返回值是相同的,不同的輸入可能對應同一個輸出。
所有的輸入值均勻地對應到輸出上。
一致性雜湊、快取結構、負責均衡。

已有3臺機器,但現在要新增1臺,進行資料遷移,則取模的資訊變了,如何解決?

引入一致性雜湊,既能負載均衡,又能自由刪減機器。

這個後面再去看,暫且放著

另:

1、如何構造雜湊函式:

直接定址法:h(key)=a*key+b,取線性函式值作為雜湊地址。
除留餘數法:散列表表長為m,取不大於m但最接近m的質數p,h(key)=key%p。
數字分析法:設關鍵字是r進位制數,選取數碼均勻的若干位作為雜湊地址。
平方取中法:取關鍵字平方值的中間幾位作為雜湊地址。
摺疊法:將關鍵字分割成位數相同的幾部分,取這幾部分的重疊和作為雜湊地址。
2、處理衝突的問題

開放定址法:線性探測法、平方探測法、再雜湊法、偽隨機序列法
拉鍊法:將所有同義詞儲存在一個線性連結串列中,由雜湊地址唯一標識。
雜湊查詢法:取決於雜湊函式,處理衝突的方法和裝填因子。
28、設計RandomPool結構
設計一種結構,在該結構中有如下三種功能:

insert(key):將某個key加入到該結構中,做到不重複加入。
delete(key):將原本在結構中的某個key移除。
getRandom(key):等概率隨機返回結構中的任何一個key。
準備兩張hash表。用size記錄是第幾個進來的。進則size++,刪則size–。

如果要刪除某個記錄,將最後一行記錄放入改記錄中,刪除最後一行記錄,size–。

在等概論返回時,即可只返回當前size大小內的隨機值所對應的key。rand()(size)。

29、轉圈列印矩陣、轉90度列印矩陣
29.1 轉圈列印矩陣
要求額外空間複雜度為O(1)。

從左上角開始,列++,加到邊後,行++,到邊後,列- -,到邊後,行- -。

重複上面四個過程。注意邊界條件。

void print_one(vector<vector>a, int tr, int tc, int dr, int tc)
{
if(tcdc)
{
for(int i=tr;i<=dr;i++)
cout<<a[i][tc]<<endl;
}
else if(tr
dr)
{
for(int i=tc;i<=dc;i++)
cout<<a[tr][i]<<endl;
}
else
{
int curr=tr;
int curc=tc;
while(curc < dc)
cout<<a[tr][curc++]<<" “;
while(curr < dr)
cout<<a[curr++][dc]<<” “;
while(curc>tc)
cout<<a[dr][curc–]<<” “;
while(curr>tr)
cout<<a[curr–][tc]<<” ";
}
}
void printmatrix(vetcor<vetcor>a)
{
int tr = 0;
int tc = 0;
int dr = a.size() - 1;
int dc = a[0].size() - 1;
while(tr<=dr && tc<=dc)
print_one(a, tr++, tc++, dr–, dc–);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
29.2 轉90度列印矩陣
類似於轉圈列印,原地調整。

主要內容的虛擬碼為:

int temp = a[tr][tc+i] //temp = 列+i
a[tr][tc+i] = a[tr+i][dc] //列+i=行+i
a[tr+i][dc] = a[dr][dc-i] //行+i = 列-i
a[dr][dc-i] = a[dr-i][tc] //列-i = 行 - i
a[dr-i][tc] = temp //行-i = temp
1
2
3
4
5
30、之字型列印矩陣
一直找對角線,從左上角開始。

準備兩個點a,b,作為記錄對角線的兩個端點位置。

a點從左上角開始,向右走,走到矩陣邊界後就向下走。

b點從左上角開始,向下走,走到矩陣邊界後就向右走。

每次a,b都各走一步,然後列印以a,b為端點的對角線上的元素。每次用一個bool值記錄列印的方向,用來標記方向交替。

31、在行列有序的矩陣中找數
時間複雜度為O(M+N)。

從右上角開始,如果要查詢得數x比右上角這個數大,則繼續向下找,否則就向左找。

直到走到邊界,可判斷這個數是否在矩陣中。

bool if_find(vecotr<vector>a, int x)
{
int i=0;
int j=a[0].size()-1;
while(i<a.size() && j>=0)
{
if(a[i][j] == x)
return true;
else if(a[i][j] > x)
j–;
else
i++;
}
return false;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
32、列印兩個有序連結串列的公共部分
33、判斷一個連結串列是否是迴文結構
34、將單向連結串列按照某值劃分為左邊小、中間相等、右邊大結構,且內部保持原有順序
35、複製含有隨機指標節點的連結串列
36、兩個單鏈表相交的一系列問題
37、反轉單向、雙向連結串列
五、第五次課(2017.11.25)
38、隨時找到資料流的中位數
39、金條切塊
40、求收益問題
41、摺紙問題
42、二叉樹的前序、中序、後序遍歷的遞迴與非遞迴實現
42.1 前序
遞迴寫法:

void preOrder(TreeNode *root)
{
if(root==NULL)
return ;
cout<val<<" ";
preOrder(root->left);
preOrder(root->right);
}
1
2
3
4
5
6
7
8
非遞迴:

利用棧,將頭節點放入棧中,彈出並列印。然後將右節點和左節點依次入棧。重複彈出棧頂並列印,右、左依次入棧的過程。

void oreOrder(TreeNode root)
{
if(root==NULL)
return ;
stack<TreeNode
>s;
s.push(root);
TreeNode *p = root;
while(!s.empty())
{
p = s.pop();
cout<val<<" ";
if(p->right!=NULL)
s.push(p->right);
if(p->left!=NULL)
s.push(p->left);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
42.2 中序
遞迴:

void inOrder(TreeNode *root)
{
if(root==NULL)
return ;
inOrder(root->left);
cout<val<<" ";
inOrder(root->right);
}
1
2
3
4
5
6
7
8
非遞迴:

利用棧,如果頭節點不為空,則入棧,頭節點等於其左孩子。一直到頭節點為空,則彈出棧頂,並且將棧頂右孩子入棧。

void inOrder(TreeNode *root)
{
if(root==NULL)
return ;
stack<TreeNode *>s;
s.push(root);
TreeNode *p=root;
while(!s.empty() || p!=NULL)
{
if(p!=NULL)
{
p = p->left;
s.push§;
}
else
{
p = s.pop();
cout<val<<" ";
p = p->right;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
42.3 後序
遞迴:

void postOrder(TreeNode *root)
{
if(root==NULL)
return ;
postOrder(root->left);
postOrder(root->right);
cout<val<<" ";
}
1
2
3
4
5
6
7
8
非遞迴:

利用棧和前序的非遞迴遍歷,前序是頭節點入棧,彈出並列印,右左節點依次入棧,重複彈出棧頂列印。

這樣得到的是中左右,我們可以得到中右左,就是按照前序非遞迴方法,頭節點入棧,彈出並列印,左右節點依次入棧,重複彈出棧頂列印。

這樣得到中右左後,逆序後就是左右中,即後序遍歷。

需要準備兩個棧。

void postOrder(TreeNode *root)
{
if(root==NULL)
return ;
stcak<TreeNode *>help;
stcak<TreeNode *>s;
help.push(root);
TreeNode *p=root;
while(!help.empty())
{
p = help.pop();
s.push§;
if(p->left)
help.push(p->left);
if(p->right)
help.push(p->right);
}
while(!s.empty())
{
p = s.pop()
cout<val<<" ";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
43、較為直觀的列印二叉樹
先從最右邊開始列印。

44、在二叉樹中找到一個節點的後繼節點
後繼節點:對二叉樹進行中序遍歷後,一個節點的後一個節點就是後繼節點。即樹的最右邊的那個節點(中序遍歷最後一個節點)是沒有後繼節點的。

建立一個新型二叉樹,有val、左節點、右節點、父節點。

對於節點s,

如果有右子樹,則後繼節點為右子樹上最左的節點。

如果沒有右子樹,但是是父親的左孩子,則父親節點就是後繼節點;如果是父親的右孩子,則繼續向上找。

TreeNode *getleftnode(NodeTree *root)
{
if (rootNULL)
return NULL;
while(root->left!=NULL)
root = root->left;
return root;
}
TreeNode *getnode(NodeTree *root)
{
if(root
NULL)
return NULL;
if(root->right!=NULL)
return getleft(root->right;)
else
{
TreeNode *parent = root.parent;
while(parent != NULL && parent->left != NULL)
parent = parent->parent;
return parent;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
45、並查集
46、布隆過濾器
六、第六次課(2017.11.26)
47、二分搜尋
48、字首樹及其六個應用
字首樹是字典樹,沒有左右孩子之分,是多叉樹。

例如,將abc、bcd、abd、bck生成共同字首樹,則為:

  o

a/ \b
o o
b/ \c
o o
c/ \d d/ \k
o o o o
1
2
3
4
5
6
7
從空節點開始,用邊來表示字元,有這個邊的話,就共用,沒有就加邊。

48.1 找B陣列中哪些串是A陣列中串的字首
48.2 查詢某個字串是否在陣列中
48.3 查詢某個字串出現否,且出現幾次
48.4 二叉樹曾經有多少個路徑經過當前節點
48.5 刪除字串
48.6 整型陣列中都是3位數,是否能得到最趨近999的和
49、圖、無向圖、有向圖、鄰接表、鄰接矩陣
50、圖的寬度優先遍歷
51、圖的深度優先遍歷
52、拓撲排序
53、最小生成樹:Kruskal演算法、Prim演算法
54、Dijkstra演算法
七、第七次課(2017.12.02)
55、遞迴概念
56、漢諾塔問題
57、列印一個字串的全部子序列
58、列印一個字串的全部序列
59、母牛生小牛問題、人走臺階問題
60、給定數字字串,轉換成字母字串
61、逆序棧,不用額外空間,遞迴
62、動態規劃概念
63、陣列從左上走到右下的最小路徑和
64、陣列中的數是否能累加到aim
65、01揹包問題
八、第八次課(2017.12.03)
66、堆的應用、攻克城市
67、陣列代表容器,求max裝水量
68、最大的leftmax與rightmax之差的絕對值
69、求最大子陣列和
70、字串是否可迴圈右移得到某字串
71、字串右移K位後的字串,原地調整
72、生成視窗最大值陣列
73、拼接字串,使其字典順序最小
74、佔用會議室問題
九、第九次課(2017.12.06)
75、Morris遍歷
76、搜尋二叉樹
77、平衡樹AVL
78、SB樹、紅黑樹
79、跳錶