算法
牛客網編程題常見的編譯錯誤:
(1)常常有邏輯是對的,但是打印時沒有輸出結果的情況
原因:一般是輸入的測試數據有多組,但編寫的程序中沒有使用循環接收輸入數據,直接收了一組測試數據造成的;
(2)對於二叉樹等類似題型,提示堆棧溢出,遞歸或循環超出範圍的情況
原因:一般是首次進入樹序列時沒有判斷樹的根節點是否爲空;
算法描述問題:
答:描述算法的方法有多種,常用的有自然語言、結構化流程圖、僞代碼和PAD圖等,其中最普遍的是流程圖。
算法描述:
自然語言 也就是文字描述;
流程圖 特定的表示算法的圖形符號;
僞語言 包括程序設計語言的三大基本結構及自然語言的一種語言;
類語言 類似高級語言的語言,例如,類PASCAL、類C語言。
算法複雜度問題:
1)對數據進行壓縮存儲可以降低算法的空間複雜度;答:對於三個複雜度符號:可以簡單的理解爲θ是一個區間,O是上限(也就是最壞情況),Ω爲下限(相當於最好情況),都是描述隨輸入量n的增長算法所花費的時間的增長情況。而一般情況下,我們都是使用o來表示複雜度(即最差的情況)。
時間複雜度計算方式:尋找算法中執行頻度最高的那個語句,算出其執行次數F(n),去掉F(n)的係數可得到f(n),那麼時間複雜度就是o(f(n)),其中n趨於無窮大。一般情況下,對於n趨於無窮大時,若頻次始終是常數的,那麼f(n)=1,所以時間複雜度爲o(1);若頻次是跟n爲線性關係,那麼f(n)=n,即時間複雜度爲o(n);其他以此類推。
空間複雜度計算方式:類似於時間複雜度計算。
1、各種排序算法
答:常用的幾種排序算法可參考:https://zhuanlan.zhihu.com/p/27232454不穩定排序:排序前後,相同元素相對位置關係發生改變;
教科書上的八種排序算法,按穩定排序和不穩定排序分類:
穩定排序:插冒歸基
不穩定排序:快選堆希
注:基數排序的時間複雜度一般也可表示爲O(r*n),當r較小時就近似爲O(n);
如上表所示:
(1)冒泡、選擇、直接插入排序統稱爲簡單排序,時間複雜度均爲O(n^2);
(2)希爾排序的時間複雜度與比較步長有關,一般可認爲O(n^1.3);
(3)選擇排序和堆排序的時間複雜度與初始序列排列順序無關;
(4)空間複雜度有3個不是o(1),分別是快、歸、基,其中快排又是相對較小的。
(5)比較排序的時間複雜度最多可以減少到O(nlogn),基數排序不是比較類排序,所以可以做到O(n);
(1)直接插入排序
答:①就是對一段數據序列,從第一個開始像摸撲克牌時那樣插入排序,如下圖:
void insertion_sort(vector<int> &v) //插入排序算法
{
int temp = 0;
for (int i = 1; i < v.size(); i++)
{
if (v[i - 1] > v[i])
{
temp = v[i];
for (int j=i-1; j > =0 && v[j] > temp; j--)
{
v[j+1] = v[j];
}
v[j+1] = temp;
}
}
}
②直接插入排序的優化:在查找插入位置時使用二分查找法,因爲前面都是有序序列,使用二分查找法速度更快;
(2)希爾排序
答:希爾(Shell)排序又稱爲縮小增量排序,它是一種插入排序。它是直接插入排序算法的一種威力加強版,即把原來的比較步長加大。排序如下例子:如上圖,初始時,有一個大小爲 10 的無序序列。
在第一趟排序中,我們不妨設 gap1 = N / 2 = 5,即相隔距離爲 5 的元素組成一組,可以分爲 5 組。
接下來,按照直接插入排序的方法對每個組進行排序。
在第二趟排序中,我們把上次的 gap 縮小一半,即 gap2 = gap1 / 2 = 2 (取整數)。這樣每相隔距離爲 2 的元素組成一組,可以分爲 2 組。
按照直接插入排序的方法對每個組進行排序。
在第三趟排序中,再次把 gap 縮小一半,即gap3 = gap2 / 2 = 1。 這樣相隔距離爲 1 的元素組成一組,即只有一組。
按照直接插入排序的方法對每個組進行排序。此時,排序已經結束。
需要注意一下的是,圖中有兩個相等數值的元素 5 和 5 。我們可以清楚的看到,在排序過程中,兩個元素位置交換了。
所以,希爾排序是不穩定的算法。時間複雜度:希爾排序的時間複雜度跟比較步長的選擇有關,一般可以做到O(n^1.3);
代碼實現:
void ShellInsert(vector<int> &v, int step)//比較希爾排序代碼與簡單插入排序代碼的異同
{
int temp=0;
for(int i=step; i<v.size(); i++)
{
if(v[i]<v[i-step])
{
temp=v[i];
for(int j=i-step; j>=0 && v[j]>temp; j=j-step)
{
v[j+1]=v[j];
}
v[j+1]=temp;
}
}
}
void ShellSort(vector<int> v, vector<int> step)//最終排序
{
//按增量數組step依次對序列做希爾排序
for(int i=0; i<step.size(); i++)
{
ShellInsert(v, step[i]);
}
}
(3)冒泡排序
答:①冒泡排序是數據前後相鄰數據比較,前比後大則兩者交換,之後繼續向後推進比較,一輪下來最大的數據會出現在序列最後。普通的冒泡排序時間複雜度始終是n^2。實現代碼略;
②針對冒泡排序的改進,即某一趟冒泡排序後沒有任何元素交換位置,則結束排序——設標誌位。改進部分如下:
void(vector<int> v)
{
for(int i=0; i<v.size()-1;i++)
{
bool flag=false;
for(int j=0; j<v.size()-i;j++)
{
if(v[j] > v[j+1])
{
swap(v[j],v[j+1]);
flag=true;//有交換就將標誌位置位
}
}
if(!flag)//若一趟結束都沒有一次交換,表示序列已經有序
break;
}
}
(4)快速排序
答:確定一個基數,先從後向前尋找一個比基數小的數(若沒找到,就繼續找),交換。然後轉換比較方向,從前向後直到找到比基數大的,交換;再重複前面的過程。快排具體實現就是:從右找到第一個小於poviot(一般取序列中第一個數)的數,與之交換,同時left加1;然後再從左找到第一個大於poviot的數,與之交換,同時right減1;這樣循環往復,直到與poviot比較的左右數據的下標相同(left=right)爲止,此時poviot右邊的數都大於左邊,左邊的數都大於右邊。(注意:無論poviot被移到哪個位置,都是和它作比較)例如:取key=49
49 38 65 97 76 13 27 原始數組 k=49
27 38 65 97 76 13 49 l=0,r=6(從後向前)
2738 65 97 76 13 49 l=1,r=6(從前向後,未找到)
27 38 49 97 76 13 65 l=2,r=6(從前向後)
27 38 13 97 76 49 65 l=2,r=5(從後向前)
27 38 13 49 76 97 65 l=3,r=5(從前向後)
27 38 13 49 76 97 65 l=3,r=4(從後向前,未找到)
27 38 13 49 76 97 65 l=3,r=3(從後向前,未找到)
注意:快速排序中最快速情況是:每一趟排序的基準值(一般第一個數據)都可以在一趟排序完成後,將當前序列平均分爲兩個個數相等的序列。最差的情況是,每次選取的基準數據都是當前序列中最小或最大值,即當前序列是有序序列,此時其會退化爲冒泡排序。簡單總結:快排相比其他排序算法最具優勢的情況是數值序列完全無序,最不具優勢的情況是數值序列基本有序;
代碼:
void quicksort(vector<int> &v,int left, int right)
{
if(left < right)//false則遞歸結束
{
int key=v[left];//基數賦值
int low = left;
int high = right;
while(low < high) //當low=high時,表示一輪分割結束
{
while(low < high && v[high] >= key)//v[low]爲基數,從後向前與基數比較
{
high--;
}
swap(v[low],v[high]);
while(low < high && v[low] <= key)//v[high]爲基數,從前向後與基數比較
{
low++;
}
swap(v[low],v[high]);
}
//分割後,對每一分段重複上述操作
quicksort(v,left,low-1);
quicksort(v,low+1,right);
}
}
快排更多詳情參考:http://blog.csdn.net/xiongchao99/article/details/74524807#t3
(5)選擇排序
①每一次都遍歷數據序列,從待排序的數據元素中選出最小(或最大)的一個元素,順序放在被排序序列第一個位置,直到全部待排序的數據元素排完(第一輪:用第一個數與後面數據比較,後面小就與之交換位置,繼續用交換後的第一個和後續數據比較……,第二輪:用第二個數據和後面比較,類似第一輪方式,……,直到比較完所有數據)。 選擇排序是不穩定的排序方法。總的比較次數N=(n-1)+(n-2)+...+1=n*(n-1)/2,與數據的初始排列順序無關。實現代碼略。
②除此之外還有樹形選擇排序,即錦標賽排序。如一個數據序列中找出最大的和第二大的數,用競標賽思想解決最好:如有序列ABCDEFGH共8個數據的序列,找出最大和第二大的兩個,需要比較的次數,見下圖:
由圖可知,找最大值用了7次比較,第二大值用了2次比較,共計用9次比較。
實現代碼略;(6)堆排序
答:(1)堆排序是樹形選擇排序的改進型,其避免了後者較大的空間複雜度;注意:使用的最小/最大堆都是完全二叉樹;
首先可以看到堆建好之後堆中第0個數據是堆中最小的數據。取出這個數據,再根據章節二中數據結構的堆刪除方式,執行下堆的刪除操作並進行堆恢復工作。這樣堆中第0個數據又是堆中最小的數據,重複上述步驟直至堆中只有一個數據時就直接取出這個數據。
由於堆也是用數組模擬的,故堆化數組後,第一次將A[0]與A[n - 1]交換,再對A[0…n-2]重新恢復堆。第二次將A[0]與A[n – 2]交換,再對A[0…n - 3]重新恢復堆,重複這樣的操作直到A[0]與A[1]交換。由於每次都是將最小的數據併入到後面排好序的數據序列前面,故操作完成後整個數組就有序了。注意使用最小堆排序後是遞減數組,反之,最大堆排序後是遞增數組。
堆頂刪除後的排序也可見下例:
根據堆的刪除規則,刪除操作只能在堆頂進行,也就是刪除0元素。
然後讓最後一個節點放在堆頂,做向下調整工作,讓剩下的數組依然滿足最小堆。
刪除0後用8填充0的位置,爲[8,3,2,5,7,4,6]
然後8和其子節點3,2比較,結果2最小,將2和8交換,爲:[2,3,8,5,7,4,6]
然後8的下標爲2,其兩個孩子節點下標分別爲2*2+1=5,2*2+2=6
也就是4和6兩個元素,經比較,4最小,將8與4交換,爲[2,3,4,5,7,8,6]
這時候8已經沒有孩子節點了,調整完成。
每次堆刪除後的堆恢復時間複雜度爲O(logn),堆排序總時間複雜度=O(nlogn);空間複雜度=O(1);
//注意:爲了計算方便,默認數組從下標1開始,即數組v的首元素空着;
void HeapAdjust(vector<int> v, int start, int end)//start是待調整堆的堆頂元素下標,end即爲待調整堆的最後一個元素下標
{
int top=v[start];
for(int j=2*start; j<=end; j=2*j)
{
if(j<end && v[j]<v[j+1])
j++;
if(top<v[j])
{
v[start]=v[j];
start=j;
}
else
break;
}
v[start]=top;
}
void HeapSort(vector<int> &v) //最終排序
{
//建堆(從下往上進行子堆調整即可實現)
for(int i=v.size()/2; i>0; i--) //i=v.size()/2是倒數第二層的最後一個非葉子節點
HeapAdjust(v, i, v.size()); //本來end應該是當前子堆的最後一個元素,但使用整堆的最後一個元素下標依然可以;
//取堆頂元素與堆末尾交換並調整剩下的前半部分堆元素
for(int j=v.size(); j>=1; j--)
{
swap(v[1], v[j]);
HeapAdjust(v, 1, j-1);
}
}
(2)除此之外,C++的STL中也有建堆和堆調整的函數可調用,有make_heap()。用STL函數實現堆排序具體方式如下:
①代碼簡寫:
vector<int> v;
make_heap(v.begin(),v.end());//建堆
for(;;){
……//交換堆頂和堆尾
make_heap(v.begin(),v.end()-i);//堆調整(用建堆函數實現)
}
②用pop_heap()/push_heap()可以實現堆頂刪除調整和堆尾插入調整:
vector<int> v;
pop_heap(v.begin(),v.end());//先pop_heap,然後在容器中刪除
v.pop_back();
v.push_back(temp);//先在容器中加入,再push_heap
push_heap(v.begin(),v.end());
堆的刪除和插入的語句順序必須如上述一樣:
刪除堆頂pop_heap實際並沒有刪除,只是將堆頂元素放到堆尾,然後對前面剩下的對元素進行堆調整。要實實在在的刪除後面就還需要調用pop_back();
插入元素push_heap則要注意必須在push_back語句操作後面,否則無法實現堆調整。
(3)top K問題一般可以使用的算法有堆排序、快排、選擇排序。
其中,堆排序用的比較多,因爲對於大量數據,可實現NlogK的實現複雜度。其中,具體的可以逐個元素的對堆進行刪除/插入,遍歷完所有元素後得到的堆元素就是TOP K。
注意:最大的K個元素用最小堆(小根堆),相反最小的K個元素用最大堆(大根堆);
這裏給出兩種實現最大K個元素的代碼:①make_heap調整堆:
vector<int> GetLeastNumbers_Solution(vector<int> input, int k){
int len=input.size();
if(len<=0||k<=0||k>len)
return vector<int>();
vector<int> v(input.begin(),input.begin()+k);//用測試數組前k個元素初始化堆數組
make_heap(v.begin(),v.end());//默認最大堆,要建立最小堆可以添加第三個參數greater<int>()
for(int i=k;i<input.size();i++){
if(input[i]>v[0]){ //逐個替換堆頂並調整
v.push_back(input[i]);
swap(v[0],v[k]);
v.pop_back();
make_heap(v.begin(),v.end());
}
}
return v;
}
②刪除/插入調整堆(注意pop_heap和pop_back以及push_heap和push_back的順序):vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
int len=input.size();
if(len<=0||k<=0||k>len)
return vector<int>();
vector<int> v(input.begin(),input.begin()+k);
make_heap(v.begin(),v.end());
for(int i=k;i<input.size();i++){
if(input[i]<v[0]){
pop_heap(v.begin(),v.end());//先pop_heap,然後在容器中刪除
v.pop_back();
v.push_back(input[i]);//先在容器中加入,再push_heap
push_heap(v.begin(),v.end());
}
}
sort(v.begin(),v.end());
return v;
}
(7)歸併排序
答:n個元素k路歸併排序的歸併趟數:s=logk(n);常見的歸併排序是2路歸併排序,其時間複雜度=log(n),空間複雜度=O(n);//歸併兩個子序列
void Merge(vector<int> v1, vector<int> &v2, int start, int mid, int end) //用一個序列裝兩組數組,使用start、mid、end區分不同序列
{
//將分組v1[start,...., mid]與分組v1[mid+1, ... ,end]歸併爲一個數組序列
int i=start, j=mid+1,k=0;
while(i<= mid && j<=end )
{
if(v1[i]<v1[j])
v2[k++]=v1[i++];
else
v2[k++]=v1[j++];
}
if(i<=mid)
v2[k, ... ,end]=v1[i, ... ,mid]; //簡要表示
else if(j<=end)
v2[k, ... ,end]=v1[j, ... ,end]; //簡要表示
//將排好序的新序列v2覆蓋到原序列v1中
v1[start, ..., end]=v2[0, ..., v2.size()-1]; //簡要表示
}
//最終排序(先分割再歸併,遞歸實現)
vector<int> v2; //創建輔助數組(最終長度爲n)
void MergeSort(vector<int> &arr, int start,int end)
{
if(start<end)
{
int mid=(start+end)/2;
MergeSort(arr, start, mid); //遞歸生成左子節點(分割出左序列)
MergeSort(arr, mid+1, end); //遞歸生成右子節點(分割出右序列)
Merge(arr, v2, start, mid, end); //對樹當前層上述兩個序列進行歸併
}
}
void mergeSort(vector<int>& data, int start, int end) {
// 遞歸終止條件
if(start >= end) {
return 0;
}
// 遞歸
int mid = (start + end) / 2;
mergeSort(data, start, mid);
mergeSort(data, mid+1, end);
// 歸併排序,並計算本次逆序對數
vector<int> copy(data); // 數組副本,用於歸併排序
int foreIdx = mid;// 前半部分的指標
int backIdx = end;// 後半部分的指標
int counts = 0;// 記錄本次逆序對數
int idxCopy = end;// 輔助數組的下標
while(foreIdx>=start && backIdx >= mid+1) {
if(data[foreIdx] > data[backIdx])
copy[idxCopy--] = data[foreIdx--];
else
copy[idxCopy--] = data[backIdx--];
}
while(foreIdx >= start) {
copy[idxCopy--] = data[foreIdx--];
}
while(backIdx >= mid+1) {
copy[idxCopy--] = data[backIdx--];
}
for(int i=start; i<=end; i++) {
data[i] = copy[i];
}
}
基數排序——時間複雜度O(d(k+n)),要求:d位數,每個數位有k個取值。一般情況下k和d較小,所以時間複雜度=O(n);
桶排序——時間複雜度O(n),要求:被排序數在某個範圍內,範圍過大的話桶的個數會過多;
(8)基數排序
答:多關鍵字排序,舉例說明:2°、再次入桶,不過這次以十位數的數字爲關鍵字,進入相應的桶,同一桶內有序;
3°、再次按順序取出,排序完成;
2、KMP算法
答:(1)KMP算法是字符串匹配算法,它由簡單字符串匹配(BF)轉化而來。其中,若串長爲n,模式串長爲m,則BF算法(普通匹配算法):時間複雜度O(m*n);空間複雜度O(1);
KMP算法:時間複雜度O(m+n);空間複雜度O(n);
KMP算法需要模式函數值數組next[m],用於輔助。
例如,在S=”abcabcabdabba”中查找T=”abcabd”,如果使用KMP匹配算法,當第一次搜索到S[5] 和T[5]不等後,S下標不是回溯到1,T下標也不是回溯到開始,而是根據T中T[5]==’d’的模式函數值(next[5]=2,爲什麼?後面講),直接比較S[5] 和T[2]是否相等,因爲相等,S和T的下標同時增加;因爲又相等,S和T的下標又同時增加,最終在S中找到了T。如圖:
所以,KMP算法相比普通匹配算法最大的優點就是:主字符串S的指針不需要回溯。
(2)next數組求取方法:
3、折半查找法(二分查找法)
答:要求:
1.必須採用順序存儲結構;
2.必須按關鍵字大小有序排列(不一定要升序)。
方法:取正中間進行比較,小則丟掉正中間被比較數大的一側所有數據,繼續採用二分查找比較小的一側數據。查找中,偶數個數據取正中間靠近起始方向的數據比較,奇數個數據取正中間的。
時間複雜度:o(lgN)
4、螞蟻爬行算法
答:n只螞蟻以每秒1cm的速度在長爲Lcm的竹竿上爬行。當螞蟻看到竿子的端點時就會落下來。由於竿子太細,兩隻螞蟻相遇時,它們不能交錯通過,只能各自反方向爬行。對於每隻螞蟻,我們只知道它離竿子最左端的距離爲xi,但不知道它當前的朝向。請計算所有螞蟻落下竿子的最短時間和最長時間。
問題的要點:螞蟻相遇後反方向爬行當做穿透對方繼續爬行。故最大時間就是離某一端點最遠的螞蟻用時,最小時間則爲離某端點最近的螞蟻用時中的最大者。
5、漢諾塔(Hanoi塔)
問題:漢諾塔問題中有三根杆子A,B,C。A杆上有N個(N>1)穿孔圓盤,盤的尺寸由下到上依次變小。要求按下列規則將所有圓盤移至C杆:
①每次只能移動一個圓盤;
②每個杆上大盤不能疊在小盤上面;
③根據A上原有圓盤個數k,要完成A上所有圓盤移出至C需要移動次數爲。
漢諾塔移動次數的公式:f(k+1)=2*f(k)+1;(其中,f⑴=1,f⑵=3,f⑶=7)
爲什麼呢?假設有4個圓盤,那麼移動過程可以如下描述:
①中其中將1,2,3號移動到B,移動次數爲f(3);
②中只是將最底下的圓盤移動到空杆C上,那麼移動次數就是1;
③中將A作爲輔助,移動B上的1,2,3號到C上,移動次數同①,仍然爲f(3);
故總次數爲:f(4)=2*f(3)+1;
以上移動流程具有普適性,可以推廣到k=n,故可得公式:f(k+1)=2*f(k)+1。
6、常見的電梯調度算法
答:電梯調度算法:
1)電梯有移動方向,各樓層的請求有請求方向,這裏維護一個請求表(記錄請求ID,請求方向,該請求的停靠樓層);
2)電梯按照一個方向移動,直到該方向沒有請求,不會根據某一層的請求方向突然改變電梯的移動方向。但是注意:電梯在移動過程中只處理與“電梯移動方向”相同請求方向的請求。如電梯向下移動,只處理電梯下方樓層的請求,且該請求的方向也向下(停靠樓層請求無方向)。若請求樓層在向下方向,但請求方向不是向下,是不做處理的;
3)沒完成一個請求,就從請求表中刪除該請求記錄;
4)若移動方向上已經沒有請求(這個請求不僅包括請求表中的請求樓層,還包括停靠樓層),但電梯移動方向的反方向有請求,就把電梯移動方向置位爲反方向;
實際上,電梯調度算法和一些操作系統調度算法如磁盤尋道是類似的。
請看下面例子:
7、動態規劃(DP)問題
for(int i=1;i<=N;i++)
{
for(int j=1;j<=M;j++)
{
if(v[i]<=j)
{
dp[i][j] = dp[i-1][j-v[i]]+v[i]>dp[i-1][j] ? dp[i-1][j-v[i]]+v[i]:dp[i-1][j];
}
else
dp[i][j]=dp[i-1][j];
}
}
其中,dp[][]數組存儲的就是價值,i是物件編號,j是允許的最大重量,v是單件價值;for (int i=1; i<=N; i++)
for (int j=M; j>=1; j--)
{
if (weight[i]<=j)
{
f[j]=max(f[j],f[j-weight[i]]+value[i]); //被修改的f[j]這一輪循環後續部分就不會再用了,所以直接用一個數組即可
}
}
for (int i=1; i<=N; i++)
for (int j=1; j<=M; j++)
{
for(int k=1;k<K;k++)
{
if (k*weight[i]<=j)
{
f[i][j]=max(f[i-1][j],f[i-1][j-k*weight[i]]+k*value[i]);
}
else
break;
}
}
}
for (int i=1; i<=N; i++)
for (int j=M; j>=1; j--)
{
if (weight[i]<=j)
{
f[j]=max(f[j],f[j-weight[i]]+value[i]);
}
}
8、英語句子按單詞爲單位逆序
9、根據3條邊求三角形面積——海倫公式
area=sqrt(s*(s-m_a)*(s-m_b)*(s-m_c));
if((m_a+m_b>m_c) && (m_a+m_c>m_b) && (m_b+m_c>m_a)) //這個判斷一定需要有
{
double s=(m_a+m_b+m_c)/2; //算法主要部分
double area=sqrt(s*(s-m_a)*(s-m_b)*(s-m_c));
}
10、迴文序列相關問題
int maxLen2(string str)
{
string s;
//添加輔助符#
s.push_back('#');
for(int k=0;k<str.size();k++){
s.push_back(str[k]);
s.push_back('#');
}
cout<<s<<endl;
//依次把每個字符作爲對稱中心進行最長迴文判斷
int len = s.size();
int maxlen=0;
for (int i = 1; i < len-1; i++) //s的首尾都是#,不屬於原字符串,可不計算
{
int count=1;
while(i-count>=0&&i+count<=len-1 && s[i-count]==s[i+count]){
count++;
}
if(maxlen<count-1)
maxlen=count-1;
}
return maxlen;
}
int maxLen3(string str)
{
string s;
//添加輔助符#
s.push_back('#');
for(int k=0;k<str.size();k++){
s.push_back(str[k]);
s.push_back('#');
}
cout<<s<<endl;
//依次把每個字符作爲對稱中心進行最長迴文判斷
int len = s.size();
//以下爲相比野蠻改進法新增的參數
int *p = new int[len]; //輔助數組(記錄每個點爲對稱軸的迴文長度)
p[0] = 1;
int mx =0, pi=0;//邊界和對稱中心
for(int i=1;i<len-1;i++) //s的首尾都是#,不屬於原字符串,可不計算
{
if(mx>i)
{
p[i]=min(mx-i+1,p[2*pi-i]);//核心
}else{
p[i]=1;
}
while(i-p[i]>=0&&i+p[i]<=len-1 && s[i-p[i]]==s[i+p[i]]){
p[i]++;
}
if(i+p[i]-1 > mx){
mx = i+p[i]-1;
pi = i;
}
}
//最大回文字符串長度
int maxlen = 0;
for(int i=1;i<len-1;i++)
{
if(p[i]>maxlen)
{
maxlen = p[i];
}
}
delete []p;
return maxlen-1;
}
定義LCS(i,j)=LCS(a1a2……ai,b1b2……bj),其中0≤i≤N,0≤j≤M.
對於1≤i≤N,1≤j≤M,有公式:
若ai=bj,則LCS(i,j)=LCS(i-1,j-1)+1;
若ai≠bj,則LCS(i,j)=Max(LCS(i-1,j-1),LCS(i-1,j),LCS(i,j-1));
#include<iostream>
#include<vector>
#include<string>
#include<algorithm>
using namespace std;
int maxLen(string s1, string s2){
int length1 = s1.size();
int length2 = s2.size();
vector<vector<int> > MaxLen(length1+1,vector<int>(length2+1)); //也可以用指針來定義二維動態數組
for (int i = 1; i <= length1; ++i)
{
for (int j = 1; j <= length2; ++j)
{
if (s1[i-1] == s2[j-1]){
MaxLen[i][j] = MaxLen[i-1][j - 1] + 1;
}
else{
MaxLen[i][j] = max(MaxLen[i - 1][j], MaxLen[i][j - 1]);
}
}
}
return MaxLen[length1][length2];
}
int main(){
string str;
while(cin>>str){
string str0=str;
reverse(str.begin(),str.end());
cout<<maxLen(str0,str)<<endl;
}
}
11、素數統計
答:有兩種方法:篩選法和開根號法;篩選法:從小到大篩去一個已知素數的所有倍數。依次刪除可被2整除,3整除……的數字,剩下的則爲素數 。
篩選法是原理:所有非素數都是素數的乘積構成的;
void getPrime0(int n){
int i,j;
bool m;
for(i = 1; i <= n; i ++){
m = true;
for(j = 2; j < i; j ++){
if(i % j == 0){
m = false;
break;
}
}
if(m){
cout << i << " ";
}
}
cout << endl;
}
bool prime(int x)
{
int y;
for(y=2;y<=sqrt(x);y++)
if (x%y==0)
return false;
return true;
}
以上是使用開根號算法的素數判斷函數。12、小明走臺階問題——一個樓梯有n級,小明一次最多跨3級,問小明走完臺階有多少種走法?
首先,假設走法總數爲f(n),那麼f(1)=1,f(2)=2,f(3)=4;
注意:這是一次最多跨三級,如果只能跨兩級就需要做相應改變。
f(1)=1
f(2)=2
f(3)=4
f(4)=7
f(5)=2*7-f(1)=13
f(6)=2*13-f(2)=24
f(7)=2*24-f(3)=44
f(8)=88-f(4)=81
f(9)=2*81-f(5)=149
f(10)=298-f(6)=274
f(11)=548-f(7)=504
f(12)=1008-f(8)=927
f(13)=1854-f(9)=1854-149=1705
f(14)=3410-f(10)=3410-274=3136
f(15)=6272-f(11)=6272-504=5768
……
int climbStairs(int n){
vector<int> v;
v.push_back(1);
v.push_back(1);
for(int i = 2; i <= n; i++){
v.push_back(v[i - 1] + v[i - 2]);
}
return v[n];
}
(2)斐波那契查找13、給定n個數的進棧序列,求出棧序列有多少種類型——卡特蘭數
答:n個數有多少種出棧序列,用卡特蘭數求:
f(n)=f(0)f(n-1)+f(1)f(n-2)+f(2)f(n-3)+……+f(n-2)f(1)+f(n-1)f(0);其中,f(0)=f(1)=1;
所以,如果有一入棧序列爲e1,e2,e3,e4,e5,那麼出棧序列就有f(5)=42種。
14、快慢指針——判斷循環鏈表及其他
答:快慢指針中的快慢指的是移動的步長,即每次向前移動速度的快慢。例如可以讓快指針每次沿鏈表向前移動2,慢指針每次向前移動1次。
(1)快慢指針可以用於判斷單循環鏈表:讓快慢指針從鏈表頭開始遍歷,快指針向前移動兩個位置,慢指針向前移動一個位置:
1)如果快指針到達NULL,說明鏈表以NULL爲結尾,不是循環鏈表;
2)如果 快指針追上慢指針,即快指針=慢指針,則表示出現了循環。
3)爲什麼慢指針步長爲1的話,快指針步長就爲2:因爲只有fastStep-slowStep=1,才能實現快慢指針一定相遇,而不是快指針越過慢指針。
代碼實現如下:
int isExitsLoop(LinkList* L) {
LinkList *fast, *slow;
fast = slow = L;
while (fast!=NULL && fast->next!=NULL)
{
slow = slow->next;
fast = fast->next->next;
if (slow == fast)
{
break;
}
}
return ((fast == NULL) || (fast->next == NULL));
}
快慢指針用於判斷有無環,有時候還要判斷環的入口位置(尤其是單鏈表局部環入口),這種情況參考另一博文:http://blog.csdn.net/xiongchao99/article/details/74524807#t15
(2)快慢指針獲取鏈表中間節點
使用快慢指針同時出發,當快指針到達終結點時慢指針剛好到達中間節點。
15、尋找二叉樹中兩個節點的最近祖先節點
答:1)若二叉樹是二叉排序樹(或叫做二叉查找樹、二叉搜索樹):
直接前序遍歷,找到一個節點m,滿足n1<m<n2,其中n1、n2是給定的兩個節點。
2)若爲普通二叉樹:
①樹節點結構體中給定父節點指針
如:
struct node{
Node * left;
Node * right;
Node * parent;
}
則算法思想:首先給出p的父節點p->parent,然後將q的所有父節點依次和p->parent作比較,如果發現兩個節點相等,則該節點就是
最近公共祖先,直接將其返回。如果沒找到相等節點,則將q的所有父節點依次和p->parent->parent作比較,直到p->parent==root。
程序實現如下:
Node * NearestCommonAncestor(Node * root,Node * p,Node * q)
{
Node * temp;
while(p!=NULL)
{
p=p->parent;
temp=q;
while(temp!=NULL)
{
if(p==temp->parent)
return p;
temp=temp->parent;
}
}
}
②若未給定父節點指針
算法思想:如果一個節點的左子樹包含p,q中的一個節點,右子樹包含另一個,則這個節點就是p,q的最近公共祖先。
程序實現:
/*查找a,b的最近公共祖先,root爲根節點,out爲最近公共祖先的指針地址*/
int FindNCA(Node* root, Node* a, Node* b, Node** out)
{
if( root == null )
{
return 0;
}
if( root == a || root == b )
{
return 1;
}
int iLeft = FindNCA(root->left, a, b, out);
if( iLeft == 2 )
{
return 2;
}
int iRight = FindNCA(root->right, a, b, out);
if( iRight == 2 )
{
return 2;
}
if( iLeft + iRight == 2 )
{
*out = root;
}
return iLeft + iRight;
}
用遞歸方式實現對樹一層一層的訪問,若left+right=2,那麼表示當前節點就是最近祖先節點。
16、雙棧排序
答:例如,實現棧數據的升序排列,即棧頂數據最大。
思路:利用一個輔助棧,每次比較排序棧和輔助棧的頂元素,如果排序棧較小直接壓入輔助棧,並彈出排序棧,否則將輔助棧的元素彈出並壓在排序棧棧頂元素的後面。如此反覆,直到排序棧沒有元素了,之後將輔助棧的元素全部導入排序棧,就完成排序。
程序實現:
class TwoStacks {
public:
vector<int> twoStacksSort(vector<int> numbers) {
stack<int> mystack,help;
for(auto i=numbers.end()-1;i>=numbers.begin();--i)
mystack.push(*i);
while(!mystack.empty())
{
if(help.empty()){
help.push(mystack.top());
mystack.pop();
}
else if(mystack.top()<=help.top())
{
help.push(mystack.top());
mystack.pop();
}
else
{
int temp=mystack.top();
mystack.pop();
mystack.push(help.top());
mystack.push(temp);
help.pop();
}
}
while(!help.empty())
{
mystack.push(help.top());
help.pop();
}
for(auto &c:numbers)
{
c=mystack.top();
mystack.pop();
}
return numbers;
}
};
17、判斷一個二叉樹結構是否爲另一個二叉樹的子結構
答:一般算法分爲兩個步驟:
(1)第一步在樹A中找到和B的根節點的值一樣的結點R;
(2)第二步再判斷樹A中以R爲根結點的子樹是不是包含和樹B一樣的結構。
C++實現代碼如下:
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
{
bool flag=false;
if(pRoot1!=NULL && pRoot2!=NULL)
{
if(pRoot1->val==pRoot2->val) //判斷根節點,相等就繼續判斷其他節點是否相等
flag=Match(pRoot1,pRoot2);
if(!flag) //否則判斷左兒子節點是否與子結構根節點相等
flag=HasSubtree(pRoot1->left,pRoot2);
if(!flag) //還不等,則判斷右兒子節點是否與子結構根節點相等
flag=HasSubtree(pRoot1->right,pRoot2);
}
return flag;
}
bool Match(TreeNode* root1,TreeNode* root2){
if(root1 == NULL && root2 != NULL) return false;
if(root2 == NULL) return true;
if(root1->val != root2->val) return false;
//if(root1->val == root2->val)
return Match(root1->left, root2->left)&&Match(root1->right, root2->right);
}
};
18、DFS
答:以二叉樹爲例(就是二叉樹的先根遍歷),其他樹或圖的DFS在此基礎上進行改進。實現代碼如下:
①遞歸方式十分簡單:
void preorder(TreeNode root){
if(root){
cout<<root->data<<' ';
preorder(root->lchild);
preorder(root->rchild);
}
}
②非遞歸方式:需要使用棧作爲輔助,兩個循環實現;
while(t || s.empty!=True){
while(t){ //只要結點不爲空就應該入棧保存,與其左右結點無關
cout<<t->data<<' ';
push(&s,t);
t= t->lchild;
}
t=pop(&s);
t=t->rchild;
}
19、BFS——隊列輔助
答:以二叉樹爲例(就是二叉樹按層遍歷),其他樹或圖的BFS在此基礎上進行改進。使用隊列queue實現,每當從隊列頭部彈出一個節點,就將該節點的子節點按先左後右的方式壓入隊尾;實現代碼如下:
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};*/
class Solution {
public:
vector<int> PrintFromTopToBottom(TreeNode* root) {
vector<int> v;
queue<TreeNode*> q;
if(root==NULL)
return v;
q.push(root);
while(q.size()>0){
int data=q.front()->val;
v.push_back(data);
if(q.front()->left!=NULL)
q.push(q.front()->left);
if(q.front()->right!=NULL)
q.push(q.front()->right);
q.pop();
}
return v;
}
};
20、貪心算法
1.建立數學模型來描述問題。
2.把求解的問題分成若干個子問題。
3.對每一子問題求解,得到子問題的局部最優解。
4.把子問題的解局部最優解合成原來解問題的一個解。
貪心策略適用的前提是:局部最優策略能導致產生全局最優解。
實際上,貪心算法適用的情況很少。一般,對一個問題分析是否適用於貪心算法,可以先選擇該問題下的幾個實際數據進行分析,就可做出判斷。
如下:
while (能朝給定總目標前進一步)
{
利用可行的決策,求出可行解的一個解元素;
}
由所有解元素組合成問題的一個可行解;
例如:n=3時,3個整數13,312,343,連成的最大整數爲34331213。
又如:n=4時,4個整數7,13,4,246,連成的最大整數爲7424613。
21、比較鑽石重量(筆試編程題)
給定兩顆鑽石的編號g1,g2,編號從1開始,同時給定關係數組vector,其中元素爲一些二元組,第一個元素爲一次比較中較重的鑽石的編號,第二個元素爲較輕的鑽石的編號。最後給定之前的比較次數n。請返回這兩顆鑽石的關係,若g1更重返回1,g2更重返回-1,無法判斷返回0。輸入數據保證合法,不會有矛盾情況出現。
測試樣例:2,3,[[1,2],[2,4],[1,3],[4,3]],4
返回:1
int cmp(int g1, int g2, int records[][2], int n)
{
// write code here
vector<int> max, min;
//先直接根據含有g1的比較組,將大於g1的值添加到數組max,小於g1的值添加到數組min
for (int i = 0; i < n; i++)
{
if (records[i][0] == g1)
{
min.push_back(records[i][1]);
}
if (records[i][1] == g1)
{
max.push_back(records[i][0]);
}
}
//然後根據重量大小關係的傳遞性,循環比較,向max中添加比max已有元素還大的值,向min中
//添加比min已有元素還小的值
int count = 0;
while (count < n) //爲什麼要比較n輪:最壞的情況是每輪只有比較序列中最後一對某元素被添加,故
{ //需要n輪纔可以保證添加完整性
count++;
for (int i = 0; i < n; i++)
{
if (records[i][0] != g1 && records[i][1] != g1)
{
if (find(min.begin(),min.end(),records[i][0])!=min.end()) //原有min數組中發現當前數值對較大者
min.push_back(records[i][1]);
if (find(max.begin(),max.end(),records[i][1])!=max.end()) //原有max數組中發現當前數值對較小者
max.push_back(records[i][0]);
}
}
}
if (find(max.begin(),max.end(),g2)!=max.end() && find(min.begin(),min.end(),g2)==min.end())
return -1;
else if (find(max.begin(),max.end(),g2)==max.end() && find(min.begin(),min.end(),g2)!=min.end())
return 1;
else
return 0;
}
22、任意進制之間互相轉換
將一個處於Integer類型取值範圍內的整數從指定源進制轉換爲指定目標進制; 可指定的進制值範圍爲[2,62];
每個數字位的可取值範圍爲[0-9a-zA-Z]; 輸出字符串的每一個都須爲有效值;反例:"012"的百位字符即爲無效值。 實現時無需考慮非法輸入。
輸入描述:
輸入爲:
源進制 目標進制 待轉換的整數值
例子:8 16 12345670
輸出描述:
整數轉換爲目標進制後得到的值
輸入例子:
8 16 12345670
輸出例子:
29cbb8
#include<iostream>
#include<string>
using namespace std;
int main(){
int source,target=0;
string str;
while(cin>>source>>target>>str){
int DecNum=0;
//區分正負數
int i=0;
if(str[0]=='-')
i=1;
else
i=0;
//轉化爲10進制
while(i<str.size()){
int num=0;
DecNum=DecNum*source;
if(str[i]<='9')
num=str[i]-'0';
else if(str[i]>='a' && str[i]<='z')
num=str[i]-'a'+10;
else if(str[i]>='A' && str[i]<='Z')
num=str[i]-'A'+36;
DecNum+=num;
i++;
}
//10進制轉化爲目標進制
string tStr;
while(DecNum>0){
string temStr;
int num=DecNum%target;
if(num<=9)
temStr=std::to_string(static_cast<long long>(num)); //VS2010未實現int轉化爲string
else if(num>=10 && num<=35)
temStr='a'+num-10;
else if(num>=36 && num<=61)
temStr='A'+num-36;
tStr=temStr+tStr;
DecNum=DecNum/target;
}
if(str[0]=='-')
tStr="-"+tStr;
cout<<tStr<<endl;
}
}
23、鏈表反轉
ListNode* ReverseList(ListNode* pHead) {
ListNode *p,*q,*r;
if(pHead==NULL || pHead->next==NULL){
return pHead;
}else{
p=pHead;
q=p->next;
pHead->next=NULL;
while(q!=NULL){
r=q->next;
q->next=p;
p=q;
q=r;
}
return p;
}
}
24、統計二進制中有多少個1
int Count1(unsigned int v)
{
int num = 0;
while(v)
{
if(v % 2 == 1)
{
num++;
}
v = v/2;
}
return num;
}
int Count2(unsigned int v)
{
unsigned int num = 0;
while(v)
{
num += v & 0x01;
v >>= 1;
}
return num;
}
int Count3(unsigned int v)
{
int num = 0;
while(v)
{
v &= (v-1);
num++;
}
return num;
}
25、複雜鏈表複製
struct RandomListNode {
int label;
struct RandomListNode *next, *random;
RandomListNode(int x) :
label(x), next(NULL), random(NULL){
}
};
複雜鏈表複製就是指複製一個一模一樣的鏈表,不是淺拷貝而是深拷貝。因爲每個節點多了一個指向任意位置的特殊指針,常見的鏈表深拷貝方式(按節點對應關係一個個創建)已經不好用了,要實現複製複雜鏈表,最好的辦法是“將新創建的節點插入到原鏈表對應節點的後面”,具體如下:鏈接:https://www.nowcoder.com/questionTerminal/f836b2c43afc4b35ad6adc41ec941dba
來源:牛客網
RandomListNode* Clone(RandomListNode* pHead)
{
if(!pHead) return NULL;
RandomListNode *currNode = pHead;
while(currNode){
RandomListNode *node = new RandomListNode(currNode->label);
node->next = currNode->next;
currNode->next = node;
currNode = node->next;
}
currNode = pHead;
while(currNode){
RandomListNode *node = currNode->next;
if(currNode->random){
node->random = currNode->random->next;
}
currNode = node->next;
}
//拆分
RandomListNode *pCloneHead = pHead->next;
RandomListNode *tmp;
currNode = pHead;
while(currNode->next){
tmp = currNode->next;
currNode->next =tmp->next;
currNode = tmp;
}
return pCloneHead;
}
26、兩個有序鏈表合併
while(i<a.size() && j<b.size()){
if(a[i]>=b[j]){
v.push_back(a[i]);
i++;
}
else{
v.push_back(b[j]);
j++;
}
}
if(i<a.size()){
……
}
else if(b.size()){
……
}
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
if(pHead1==NULL){
return pHead2;
}else if(pHead2==NULL){
return pHead1;
}
ListNode* pHead;
vector<ListNode*> v;
int len=0;
while(pHead1!=NULL && pHead2!=NULL){
if(pHead1->val<=pHead2->val){
v.push_back(pHead1);
pHead1=pHead1->next;
}else{
v.push_back(pHead2);
pHead2=pHead2->next;
}
len=v.size();
if(len>=2){
v[len-2]->next=v[len-1];
}
}
if(pHead1!=NULL){
v[len-1]->next=pHead1;
}else if(pHead2!=NULL)
v[len-1]->next=pHead2;
pHead=v[0];
return pHead;
}
};
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
if(pHead1==NULL)
return pHead2;
else if(pHead2==NULL)
return pHead1;
if(pHead1->val <= pHead2->val){
pHead1->next=Merge(pHead1->next,pHead2);
return pHead1;
}else{
pHead2->next=Merge(pHead1,pHead2->next);
return pHead2;
}
}
27、字符串全排列
vector<string> Permutation(string str){
vector<string> v;
if(str=="")
return v;
sort(str.begin(), str.end());//必須先遞增排序,使得str是最小一種排序
do
{
v.push_back(str);
}
while (next_permutation(str.begin(), str.end()));//每一次循環返回true表示找到了比str大的字符串
return v;
}
prev_permutation:
bool cmp(const char a,const char b){
return a>b;
}
vector<string> Permutation(string str){
vector<string> v;
if(str=="")
return v;
sort(str.begin(), str.end(),cmp);//此處不同
do
{
v.push_back(str);
}
while (prev_permutation(str.begin(), str.end()));
return v;
}
1,從尾部往前找第一個P(i-1) < P(i)的位置
4 6 <- 9 <- 8 <- 7 <- 5 <- 2 <- 1
最終找到6是第一個變小的數字,記錄下6的位置i-1
2,從i位置往後找到最後一個大於6的數
4 6 -> 9 -> 8 -> 7 5 2 1
最終找到7的位置,記錄位置爲m
3,交換位置i-1和m的值
4 7 9 8 6 5 2 1
4,倒序i位置後的所有數據
4 7 1 2 5 6 8 9
則347125689爲346987521的下一個排列;
vector<string> permutation(string str)
{
vector<string> v;
if(str.empty())
return v;
int length=str.size();
int fromIndex, changeIndex;
sort(str.begin(), str.end()); //先升序排列,獲取最小的字符串組合
do
{
//保存當前獲得的一種全排列組合
v.push_back(str);
fromIndex = length - 1;
//(1)向前查找第一個由大變小的元素位置
while (fromIndex > 0 && str[fromIndex] <= str[fromIndex - 1])
--fromIndex;
changeIndex = fromIndex;
if (fromIndex == 0)
break;
//(2)向後查找最後一個大於words[fromIndex-1]的元素
while (changeIndex + 1< length && str[changeIndex + 1] >= str[fromIndex - 1])
++changeIndex;
//(3)交換兩個值
swap(str[fromIndex - 1], str[changeIndex]);
//(4)對後面的所有值進行反向處理
reverse(str.begin()+fromIndex, str.end());
}
while (true);
return v;
}
1,for循環將每個位置的數據交換到第一位
swap(1,1~5);
2,按相同的方式全排列剩餘的位;
void PermutationHelp(vector<string> &ans, int k, string str) //遍歷第k位的所有可能
{
if(k == str.size() - 1)
ans.push_back(str);
for(int i = k; i < str.size(); i++)
{
if(i != k && str[k] == str[i])
continue;
swap(str[i], str[k]);
PermutationHelp(ans, k + 1, str);
}
}
vector<string> Permutation(string str) {
sort(str.begin(), str.end());
vector<string> ans;
PermutationHelp(ans, 0, str);
return ans;
}
28、單鏈表的反轉算法
答:思想:創建3個指針,分別指向上一個節點、當前節點、下一個節點,遍歷整個鏈表的同時,將正在訪問的節點指向上一個節點,當遍歷結束後,就同時完成了鏈表的反轉。
實現代碼:
ListNode* ReverseList(ListNode* pHead) {
ListNode *p,*q,*r;
if(pHead==NULL || pHead->next==NULL){
return pHead;
}else{
p=pHead;
q=p->next;
pHead->next=NULL;
while(q!=NULL){
r=q->next;
q->next=p;
p=q;
q=r;
}
return p;
}
}
29、棧作爲輔助結構的經典算法
②當前要進棧元素>stackMin棧頂元素時,stackMin棧把當前stackMin的棧頂元素再壓入一遍;
int popBottom(Stack<Integer> stack){
int result = stack.pop();
if(stack.isEmpty()){//彈出一個棧頂元素後,棧爲空了,表示該元素就是棧底元素
return result;
}else{
int last = popBottom(stack);
stack.push(result);//注意!!!這裏是把前面拿到的元素壓入,這樣棧底元素纔不會再次壓入到棧中
return last;
}
}
(6)棧中元素排序(最多使用一個輔助棧):假設棧stack是存放原來數據的,再定義一個輔助棧help,先從stack棧中取出棧頂元素pop,將pop和help中棧頂元素比較,如果pop <= help棧頂元素,將pop壓入到help棧中;如果pop > help棧頂元素,取出help棧頂元素,將其放入到stack棧中,直到help爲空或者pop
<= help棧頂元素。30、隊列作爲輔助結構的經典算法
31、將二叉搜索樹轉化爲雙向鏈表,不允許創建新節點和其他數據結構輔助
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
TreeNode* Tree2List(TreeNode* pRootOfTree){
TreeNode *Node;
if(pRootOfTree->left==NULL && pRootOfTree->right==NULL)//葉子就返回,否則繼續下面的遞歸
return pRootOfTree;
//思想:無論左右子樹,都返回子樹中最大的節點(左子樹最大節點是左子樹根,右子樹最大節點是右子樹最靠右的節點)
if(pRootOfTree->left!=NULL)
{
Node=Tree2List(pRootOfTree->left);
pRootOfTree->left=Node; //將當前節點鏈接在左子樹最大節點右邊
Node->right=pRootOfTree;
Node=pRootOfTree; //(1)返回當前樹的最大節點(當前樹的根節點)
}
if(pRootOfTree->right!=NULL)
{
Node=Tree2List(pRootOfTree->right);//(2)子樹返回值(最大節點)
TreeNode *temNode=Node;
while(temNode->left!=NULL)//獲取右子樹最小節點
temNode=temNode->left;
pRootOfTree->right=temNode;//將當前節點鏈接在右子樹最小節點的左邊
temNode->left=pRootOfTree;
}
return Node; //最後返回當前樹的最大節點(無右子樹就返回根節點(1),有右子樹就返回右子樹最大節點(2))
}
TreeNode* Convert(TreeNode* pRootOfTree)
{
if(pRootOfTree==NULL)
return NULL;
TreeNode* node=Tree2List(pRootOfTree);
while(node->left!=NULL) //要求返回最左邊第一個節點,所以作如下操作
node=node->left;
return node;
}
32、整數中1出現的次數
(2)當m表示百位,且百位對應的數爲1,如n=31156,m=100,則a=311,b=56,此時百位對應的就是1,則共有a/10(不加1,即最高兩位0-30)次是包含100個連續點。當最高兩位爲31(即a=311),本次只對應局部點00~56,共b+1次,所有點加起來共有(a%10*100)+(b+1),這些點百位對應爲1;
(3)當m表示百位,且百位對應的數爲0,如n=31056,m=100,則a=310,b=56,此時百位爲1的次數有a/10=31(不加1,最高兩位0~30);
int NumberOf1Between1AndN_Solution(int n)
{
int ones = 0;
for (int m = 1; m <= n; m *= 10) {
int a = n/m, b = n%m;
ones += (a + 8) / 10 * m + (a % 10 == 1) * (b + 1);
}
return ones;
}
33、把數組元素連接成最小的數值
static bool cmp(int a,int b){
string A="";
string B="";
A=A+to_string((long long)a);
A=A+to_string((long long)b);
B=B+to_string((long long)b);
B=B+to_string((long long)a);
return A<B;
}
string PrintMinNumber(vector<int> numbers) {
sort(numbers.begin(),numbers.end(),cmp);
string resStr;
for(int k=0;k<numbers.size();k++){
resStr+=to_string((long long)numbers[k]);
}
return resStr;
}
34、獲取第n個醜數
int GetUglyNumber_Solution(int index) {
vector<int> res(index);
res[0] = 1;
int t2 = 0, t3 = 0, t5 = 0, i;
for (i = 1; i < index; ++i)
{
res[i] = min(res[t2] * 2, min(res[t3] * 3, res[t5] * 5));
if (res[i] == res[t2] * 2)t2++;
if (res[i] == res[t3] * 3)t3++;
if (res[i] == res[t5] * 5)t5++;
}
return res[index - 1];
}
35、逆序對
int mergeSort(vector<int>& data, int start, int end) {
// 遞歸終止條件
if(start >= end) {
return 0;
}
// 遞歸
int mid = (start + end) / 2;
int leftCounts = mergeSort(data, start, mid);
int rightCounts = mergeSort(data, mid+1, end);
// 歸併排序,並計算本次逆序對數
vector<int> copy(data); // 數組副本,用於歸併排序
int foreIdx = mid;// 前半部分的指標
int backIdx = end;// 後半部分的指標
int counts = 0;// 記錄本次逆序對數
int idxCopy = end;// 輔助數組的下標
while(foreIdx>=start && backIdx >= mid+1) {
if(data[foreIdx] > data[backIdx]) {
copy[idxCopy--] = data[foreIdx--];
counts += backIdx - mid;
} else {
copy[idxCopy--] = data[backIdx--];
}
}
while(foreIdx >= start) {
copy[idxCopy--] = data[foreIdx--];
}
while(backIdx >= mid+1) {
copy[idxCopy--] = data[backIdx--];
}
for(int i=start; i<=end; i++) {
data[i] = copy[i];
}
return (leftCounts+rightCounts+counts);
}
vector<int> maxInWindows(const vector<int>& a, int k){
vector<int> res;
deque<int> d;
for(int i = 0; i < a.size(); ++i){
while(d.size()>0 && a[d.back()] <= a[i])
d.pop_back();
if(d.size()>0 && i - d.front() + 1 > k)
d.pop_front();
d.push_back(i);
if(k>0 && i+1 >= k)
res.push_back(a[d.front()]);
}
return res;
}
遍歷過程中就兩個動作:37、數據流中的中位數
priority_queue<int> qmax;
priority_queue<int,vector<int>,greater<int>> qmin;
void Insert(int num){
if(qmin.size()>0 && num>qmin.top())
qmin.push(num);
else
qmax.push(num);
if(qmax.size()>= qmin.size()+2){
qmin.push(qmax.top());
qmax.pop();
}else if(qmin.size()>= qmax.size()+2){
qmax.push(qmin.top());
qmin.pop();
}
}
double GetMedian(){
if(qmax.size()==qmin.size())
return (qmax.top()+qmin.top())/2.0;
else
return qmax.size()>qmin.size()? qmax.top():qmin.top();
}
38、快速冪
double Power(double base, int exponent) {
if(exponent==0)
return 1;
if(base==0)
return 0;
double result=1;
if(exponent>0){
result=Power(base*base,exponent/2);//每一次遞歸調用函數,base的值就變爲上一層base的2次方
if(exponent%2!=0)
result=result*base;
}
else if(exponent<0){//考慮正負次冪,將負數次冪變爲正數次冪計算
base=1/base;
exponent=-1*exponent;
result=Power(base,exponent);
}
return result;
}
假設exponent=33,這樣每次遞歸調用函數的結果如下:39、中興筆試——加密方法
unsigned long long GetBigMod(int x,int n,int mod){
if(n==0)
return 1;
int p=mod;
unsigned long long tmp=GetBigMod(x*x%p,n/2,p);
if(n%2!=0)
tmp=tmp*x%p;
return tmp;
}
int GetJiami(int x,int n,int m){
int p1=10,p2=1000000007;
return GetBigMod(GetBigMod(x,n,p1),m,p2);
}