面试常备题----数组总结篇(上)
????? 數(shù)組是我們程序員最常用的數(shù)據(jù)結(jié)構(gòu),也是筆試和面試最喜歡出的題型。要想解決好一道數(shù)組題,需要的不僅是扎實的編程基礎(chǔ),更重要的是,要有清晰的思路,因為數(shù)組題經(jīng)常是一些見都沒有見過的數(shù)學(xué)題目,需要我們當(dāng)場分析其中的規(guī)律。
????? 考察數(shù)組,最主要的是這幾個方面:查找,排序,遞歸和循環(huán),而這往往考察的就是我們編寫高效率代碼的能力。編寫能夠運行的代碼并不難,但要編寫高效的代碼卻是一門需要花時間的功夫,甚至可以說與天賦掛上鉤,有些人天生就是對算法非常敏感,能夠一下子掌握算法的精髓。但事實就是,大部分正在編程的程序員都不具備這種能力,就像我一樣。
????? 所幸,真正的事實就是:大部分人的努力程度并不足以達(dá)到與別人拼天賦的程度。
????? 依然是像我一樣,努力的程度實在是太小了!根本沒有資格抱怨自己與別人的智商差異!!
??????所以,像是我一樣的平庸之輩,最好的方法就是老老實實的認(rèn)清楚自己是個怎樣的人:沒有天賦,始終沒有別人努力,理解能力差。。。然后我們才能決定自己接下來應(yīng)該怎么辦。
?????不斷積累基礎(chǔ)知識是非常好的方法,幸運的是,很大部分的編程并不需要太高深的編程技巧,只要我們對常見的編程技巧足夠熟悉也能夠完全勝任。
????? 首先我們先從遞歸和循環(huán)開始說起,這在解決數(shù)組題目中是非常重要的基礎(chǔ)。
題目一:寫一個函數(shù),輸入n,求斐波那契數(shù)列的第n項。
??????幾乎所有講解遞歸的課本都會用這道題目。
???? ?斐波那契數(shù)列的數(shù)學(xué)表達(dá)式如下:
??????這就是遞歸的典型應(yīng)用:在函數(shù)中調(diào)用自身。
????? 數(shù)學(xué)表達(dá)式非常清晰的告訴了我們怎樣編程:?
long long Fibonacci(unsigned n) {if(n <= 0){return 0;}else if(n == 1){return 1;}else{return Fibonacci(n - 1) + Fibonacci(n - 2);} }????? 其中,long long就是int類型的擴(kuò)展,一般的int占4個字節(jié),范圍是-32768~32767,而long long則是8個字節(jié),范圍是-922337203685775808~922337203685775807,這樣是為了防止溢出,這在一些大數(shù)目的遞歸問題中非常重要。參數(shù)傳遞使用unsigned也是同樣的道理。
??????可惜的是,那些課本并沒有告訴我們這樣的解法存在非常嚴(yán)重的效率問題!
??????我們可以用樹型結(jié)構(gòu)來看一下這個遞歸過程(所有的遞歸問題都可以用樹型結(jié)構(gòu)表示):
????? 這種做法正是用循環(huán)來代替遞歸以提高效率。
????? 遞歸并不是可以被濫用的技巧,表面上使用遞歸似乎能讓我們程序員看上去非常睿智,但實際上遞歸是一種低效的做法,因為每次函數(shù)調(diào)用是有時間和空間的消耗的,需要在內(nèi)存棧中分配空間以保存參數(shù),返回地址和臨時變量,而且往棧里壓入數(shù)據(jù)和彈出數(shù)據(jù)都需要消耗時間,更可怕的是,遞歸會引起調(diào)用棧溢出的問題,因為每次進(jìn)程的棧容量是有限的,當(dāng)遞歸的層次太多的時候,就會引發(fā)問題。
????? 但是,遞歸確實可以讓代碼顯得更加簡潔,所以如果遞歸層級不是太多的情況,優(yōu)先考慮遞歸。
????? 很多實際的問題都可以看成斐波那契數(shù)列的應(yīng)用,像是這樣的題目:
????? 一只青蛙一次可以跳上1級臺階,也可以跳上2級臺階,求該青蛙跳上n級臺階總共有多少種跳法。
????? 這道題目別說是程序題,我們經(jīng)常在小學(xué)或者初中的課本中看過。仔細(xì)分析一下,就能發(fā)現(xiàn),當(dāng)一只青蛙跳上第1級臺階后,剩下的其實就是n - 1階的總跳數(shù);跳上第2階臺階后,剩下的就是n - 2階的總跳數(shù),也就是f(n - 1)和f(n - 2)。
????? 這是數(shù)學(xué)建模的能力,也是面試者所看重的。當(dāng)然,我們可能無法一下子建好模,實際的情況就是我們部分程序員的數(shù)學(xué)素養(yǎng)實在是不高,就像我一樣,對于那些高深的算法,很難一下子理解明白,但也并不是說完全沒有辦法,想想敏捷的開發(fā)思想,我們可以從最簡單的測試用例開始:
????? 首先是只有1階的情況:f(1) = 1;
????? 然后是只有2階的情況:f(2) = 2;
????? 接著是只有3階的情況:f(3) =?3 = f(2) + f(1);
????? 最后是只有4階的情況:f(4) =?5 = f(3) + f(2) = f(2) + f(1) + f(2);
????? ...
???? f(n) = f(n - 1) + f(n - 2);
???? ?也許程序員永遠(yuǎn)也不會成為數(shù)學(xué)家,因為我們都沒有受過專業(yè)的數(shù)學(xué)訓(xùn)練,但是我們必須要有發(fā)現(xiàn)規(guī)律的洞察力,這樣才能寫出好的代碼,就算一時無法洞察出規(guī)律,我們還有一個訣竅:測試用例,我們可以從最簡單的測試用例開始,就像上面一樣,只要一開始我們的代碼能夠通過f(1)的情況就行,然后再想辦法通過f(2)和f(3),最后就是通過n的情況。這就是測試用力的用處,也是我們程序員最大的法寶,只要想辦法通過當(dāng)前的測試用例就行。
???? ?數(shù)組的排序和查找是非常重要的動作,很多實際的問題都需要用到排序和查找,也因為這樣,排序和查找有很多種實現(xiàn),它們都有各自的優(yōu)缺點,但對于程序員,最重要的是快速排序和二分查找,因為使用它們能夠顯著的提高效率,尤其是在一些需要注意效率的題目中,就是變相的提示我們,需要使用到快速排序和二分查找。
????? 實現(xiàn)快速排序的關(guān)鍵就是在數(shù)組中尋找一個數(shù)字,然后把數(shù)組分成兩個部分:比該數(shù)字小的數(shù)字移到數(shù)組的左邊,大的數(shù)字則移到右邊:
int Partition(int data[], int length, int start, int end) {if(data == NULL || length <= 0 || start < 0 || end >= length){throw new std :: exception("Invalid Parameters!");}int index = RandomInRange(start, end);Swap(&data[index], &data[end]);int small = start - 1;for(index = start; index < end; ++index){if(data[index] < data[end]){++small;if(small != index){Swap(&data[index], &data[small]);}}++small;Swap(&data[small], &data[end]);return small;} }?????然后我們用遞歸的方法分別對每次選中的數(shù)字的左右兩邊進(jìn)行排序:
void QuickSort(int data[], int length, int start, int end) {if(start == end){return;}int index = Partition(data, length, start, end);if(index > start){QuickSort(data, length, start, index - 1);}else{QuickSort(data, length, index + 1, end);} }???? 快速排序雖然在總體上的平均效率是最佳的,但并不是任何時候都是最佳的,比如說數(shù)組已經(jīng)排好序,而每輪排序都是以最后一個數(shù)字作為比較的標(biāo)準(zhǔn),快速排序的時間效率就只有O(N * N)。不過一般而言,對于一個n個元素的數(shù)組,快速排序的時間復(fù)雜度為O(N *?log2N)。
???? 相比其他查找來說,二分查找的比較次數(shù)少,查找速度快,平均性能好,但前提是在有序的條件下,所以一般都是先排序,再查找:
????? 我們來看下道題目:
題目二:把一個數(shù)組的若干個元素搬到數(shù)組的末尾,就是數(shù)組的旋轉(zhuǎn)。輸入一個遞增排序的數(shù)組的一個旋轉(zhuǎn),輸出旋轉(zhuǎn)數(shù)組的最小元素。
????? 如果光是看題目,我們一下子不知道到底是怎么回事,就先以一個測試用例開始:假設(shè)數(shù)組為{1, 2, 3, 4, 5},那么旋轉(zhuǎn)數(shù)組就是{3, 4, 5, 1, 2},它的最小值就是1。
??? ? 最直觀的做法就是遍歷該數(shù)組,然后找出最小值,這樣的時間效率就是0(N)。但如果只是這么簡單的話,就不會用旋轉(zhuǎn)數(shù)組這樣聽都沒有聽過的名詞了。以前高中的時候,數(shù)學(xué)老師就經(jīng)常說:要求就是條件。這里也是如此,最好的解決方法就是利用旋轉(zhuǎn)數(shù)組的特點。
??????我們來看一下旋轉(zhuǎn)數(shù)組,就會發(fā)現(xiàn),旋轉(zhuǎn)數(shù)組實質(zhì)上是以最小值作為分界線將整個數(shù)組分為兩個部分,前面部分和后面部分都是大于等于最小值,說明這個數(shù)組已經(jīng)是經(jīng)過一定程度的排序,在經(jīng)過排序的數(shù)組中查找某個值的最好做法就是使用二分查找,這樣我們就能將時間效率控制到O(log2N)。
?????? 根據(jù)題意,兩個遞增數(shù)組的分界線就是最小值,我們先得到中間的元素,然后將它與第一個元素和最后一個元素進(jìn)行比較,如果大于等于第一個元素,說明最小值是在中間元素后面,我們可以指向第二個元素,以此類推,如果中間元素小于等于最后一個元素,說明最小值是在中間元素前面,以此類推。
????? 測試用例并不是一次就足夠,我們還要盡可能的考慮更多的情況:
1.將0個元素放在最小值后面,也就是原來的數(shù)組的情況,這時上面的思路就不管用了,因為第一個值就是最小值;
2.考慮這樣的數(shù)組{0, 1, 1, 1, 1},那么{1, 0, 1, 1, 1}就是它的旋轉(zhuǎn)數(shù)組之一,但是我們無法用二分查找找出它的最小值,因為我們無法確定中間那個1到底是在前面的遞增序列還是后面的遞增序列。這時我們就要按照順序來查找了。
??????結(jié)合上面的情況,我們可以寫出這樣的代碼:
int Min(int* data, int length) {if(data == NULL || length <= 0){throw new std :: exception("Invalid Parameters!");}int index1 = 0;int index2 = length - 1;int indexMid = index1;while(data[index1] >= data[index2]){if(index2 - index1 == 1){indexMid = index2;break;}indexMid = (index1 + index2) / 2;if(data[index1] == data[index2] && data[indexMid] == data[index1]){return MinInOrder(data, index1, index2);}if(data[indexMid] >= data[index1]){index1 = indexMid;}else if(data[indexMid] <= data[index2]){index2 = indexMid;}}return data[indexMid]; ]int MinInOrder(int* data, int index1, int index2) {int result = data[index1];for(int i = index1 + 1; i <= index2; ++i){if(result > data[i]){result = data[i];}}return result; }????? 當(dāng)我們拿到一個從未見過的情境的時候,最好的方法就是盡可能的提出不同的測試用例,考察更多的情況,這樣我們就能在不斷通過這些測試的時候完成正確的代碼。讓代碼正確地運行是我們首要的任務(wù),然后才考慮優(yōu)化的問題,當(dāng)然,如果一開始就能根據(jù)題目的要求聯(lián)想到最優(yōu)解那自然是最好不過了。
??????二分查找體現(xiàn)的是分治策略,而分治策略和遞歸可以說是一對好朋友,像是下面這道題目:
題目三:找出數(shù)組中的最大值。
?????這是非常簡單的題目,我們可以使用二分查找結(jié)合遞歸:
int GetMax(int* data, int start, int end) {int maxValue;
if(data != NULL && length > 0)
{if(end - start <= 1){if(data[start] >= data[end]){return data[start];}else{return data[end];}}int maxL = GetMax(data, start, start + (end - start) / 2);int maxR = GetMax(data, start + (end - start) / 2 + 1, end);if(maxL > maxR){maxValue = maxL;}else{maxValue = maxR;}
}return maxValue;
}
???? ?這就是所謂的分治策略。
????? 二分查找是分治策略的一種實現(xiàn),使用二分查找的條件之一就是我們知道想要找的是一個具體的值。
????? 只要題目中要求我們尋找某個值,也就是一個查找動作,我們都可以考慮使用二分查找,像是查找而快速排序的使用情況更多了,凡是涉及到"快速"這個字眼,我們都可以想想是否可以用快速排序來組織一下數(shù)組。
??????二分查找需要經(jīng)過排序的數(shù)組,但如果使用快速排序,就會破壞原來數(shù)組的結(jié)構(gòu),所以我們必須清楚最關(guān)鍵的一點:我們是否可以修改數(shù)組的結(jié)構(gòu)?
????? 如果上面的題目我們無法修改數(shù)組,那么我們就只能使用輔助數(shù)組,這也就是所謂的"用空間換取時間"的做法。
??????我們來看下面的這道題目:
題目四:找出數(shù)組中出現(xiàn)次數(shù)超過數(shù)組長度的數(shù)字。
??????同樣是從一個測試用例開始:假設(shè)數(shù)組{1, 2, 3, 2, 2, 2, 5, 4, 2},那么該數(shù)字就是2。
????? 這道題目同樣是從數(shù)組中查找某個值,我們的腦海中就會閃過二分查找,因為題目中并沒有說明是排序的,所以我們還得先排序一下,可以利用我們上面的函數(shù)Partition:
int MoreThanHalfNum(int* data, int length) {if(CheckInvalidArray(data, length){return 0;}int middle = length / 2;int start = 0;int end = length - 1;int index = Partition(data, length, start, end);while(index != middle){if(index > middle){end = index - 1;index = Partition(data, length, start, end);}else{start = index + 1;index = Partition(data, length, start, end);}}int result = data[middle];if(!CheckMoreThanHalf(data, length, result)){result = 0;}return result; }bool g_bInputInvalid = false;bool CheckInvdlidInArray(int* data, int length) {g_bInputInvalid = false;if(data == NULL || length <= 0){g_bInputInvalid = true;}return g_bInputInvalid; }bool CheckMoreThanHalf(int* data, int length, int number) {int times = 0;for(int i = 0; i < length; ++i){if(data[i] == number){times++;}}bool isMoreThanHalf = true;if(times * 2 <= length){g_bInputInvalid = true;isMoreThanHalf = false;}return isMoreThanHalf; }??????這里我們引入了全局變量g_InvalidInput,原因就是我們有兩種無效輸入情況,而且我們不能像之前那樣只是簡單的拋出異常就行,它們需要作為判斷條件來使用,但是全局變量的使用有個問題,如果其他地方會修改它的狀態(tài),那么我們必須在使用它的時候重新設(shè)置它的初始值,以確保我們的代碼能夠擁有正確的前提條件,所以最好的做法就是:除非是返回該值,否則我們應(yīng)該在測試完該條件后將它設(shè)置為初始值。
????? 這樣我們的時間效率就是O(N),但是我們會修改原來的數(shù)組,所以我們必須結(jié)合題目的特點想出另一種解法:
int MoreThanHalfNum(int* data, int length) {if(CheckInvalidArray(data, length){return 0;}int result = data[0];int times = 1;for(int i = 1; i< length; ++i){if(times == 0){result = data[i];times = 1;}else if(data[i] == result){times++;}else{times--;}} if(!CheckMoreThanHalf(data, length, result)){result = 0;}return result; }
???? 這樣的解法的思路就是我們知道,要找的數(shù)字是數(shù)組中出現(xiàn)次數(shù)最多的,我們可以在遍歷數(shù)組的時候保存兩個值:出現(xiàn)的數(shù)字和它出現(xiàn)的次數(shù),如果和它相同,次數(shù)加1,如果不相同,次數(shù)減一。
?????這種做法就不需要修改原來數(shù)組的結(jié)構(gòu),但也能達(dá)到O(N)的結(jié)果,也就是以少量的空間換取時間效率的典型做法。
?????在分析數(shù)組規(guī)律的時候,還有一種方法:動態(tài)規(guī)劃。
???? 動態(tài)規(guī)劃是數(shù)學(xué)分析中求最優(yōu)解的一種方法,它并不是一種算法,只是幫助我們更快找到規(guī)律的一種方法而已,讓我們來看一道題目:
題目五:輸入一個整型數(shù)組,既有負(fù)數(shù)又有正數(shù),數(shù)組中一個或連續(xù)的多個整數(shù)組成一個子數(shù)組,求所有子數(shù)組的和的最大值,要求時間復(fù)制度為O(N)。
???? 這是目前唯一一道指定時間復(fù)雜度為O(N)的題目,所以也就要求我們必須有分析算法時間復(fù)雜度的能力。
???? 我們還是要從測試用例開始。
???? 我們假設(shè)有這樣的數(shù)組:{1, -2, 3, 10, -4, 7, 2, -5},根據(jù)題目要求,符合要求的子數(shù)組應(yīng)該是{3, 10, -4, 7, 2},也就是輸出的和為18。
???? 這樣我們好像也找不到任何規(guī)律可言,所以我們就按照直觀的做法來分析一下:
???? 我們從數(shù)組的第一個元素開始:
???? {1}:1;
???? {1, -2}:-1,這時我們注意到,累計和為-1,無論下面的元素是什么,累計起來的和也會比下面那個元素小,所以我們選擇拋棄該子數(shù)組,重新從下一個元素開始;
???? {3}:3;
???? {3, 10}:13;
?????{3, 10, -4}:9;
???? ...
?????按照這樣的思路,我們可以確定,如果累積和不為負(fù)數(shù),我們就可以將該子數(shù)組作為最大和子數(shù)組的部分,根據(jù)這樣的思路,我們可以寫出這樣的代碼:
bool g_InvalidInput = false;int FindGreatestSumOfSubArray(int* data, int length) {if(data == NULL || length <= 0){g_InvalidInput = true;return 0;}g_InvalidInput = false;int curSum = 0;int greatestSum = 0;for(int i = 0; i < length; ++i){if(curSum <= 0){curSum = data[i];}else{curSum += data[i];}if(curSum > greatestSum){greatestSum = curSum;}}return greatestSum; }?????這里引入了一個全局變量,它的作用就是用來標(biāo)識到底是輸入無效還是子數(shù)組的和為0這兩種情況。
???? 如果是使用動態(tài)規(guī)劃,我們用f(i)來表示以第i個數(shù)字結(jié)尾的子數(shù)組的最大和,然后根據(jù)我們上面的思路,可以得出這樣的遞歸公式:
??????就像是求斐波那契數(shù)列一樣,我們可以用遞歸來編碼:
bool g_InvalidInput = false;int FindGreatestSumOfSubArray(int* data, int length) {if(data == NULL || length <= 0){g_InvalidInput = true;return 0;}g_InvalidInput = false;int n = length - 1;int curSum = 0;if(n == 0 || data[n - 1] <= 0){curSum = data[n];}else if(n != 0 && data[n - 1] > 0){curSum = data[n] + FindGreatestSumOfSubArray(data, length - 1);}return curSum; }???? 如果使用循環(huán)來代替遞歸,那么代碼就會和上面是一樣的。
???? 動態(tài)規(guī)劃適用于多階段的決策問題,我們首先要確定問題的決策對象,這里是f(i),也就是到i為止的子數(shù)組的和,然后我們再對決策過程劃分階段,這里每個數(shù)組元素都可以視為一個階段,接著再對各階段確定狀態(tài)變量,也就是確定條件,最后就是根據(jù)狀態(tài)變量確定函數(shù)表達(dá)式和各個階段狀態(tài)變量的轉(zhuǎn)移過程。
???? 但是這種思想有它的局限性,它必須滿足這樣的條件:
1.最優(yōu)化原理(最優(yōu)子結(jié)構(gòu)性質(zhì))
?????一個最優(yōu)化策略必須具有這樣的性質(zhì),不論過去狀態(tài)和決策如何,對前面的決策所形成的狀態(tài)而言,剩下的所有決策必須構(gòu)成最優(yōu)策略,也就是說,一個最優(yōu)化策略的子策略總是最優(yōu)的。
2.無后效性
?????將各個階段按照一定的次序排列好后,對于某個給定的階段狀態(tài),前面各個階段的狀態(tài)無法直接影響它未來的決策,而只能通過當(dāng)前的這個狀態(tài),換句話說,每個狀態(tài)都是過去歷史的一個完整總結(jié)。
???? 因為這是正式的數(shù)學(xué)方法,所以它非常嚴(yán)謹(jǐn)。我們來看一下第一個條件,雖然說得非常復(fù)雜,其實也就是一句話:每一步策略都必須確保是最優(yōu)解,這點在實際的分析中都能達(dá)到。最關(guān)鍵的是第二個條件,它是確定我們是否能夠使用動態(tài)規(guī)劃的關(guān)鍵條件。無后效性的真正意思就是說,我們在這個狀態(tài)采取這個策略,是因為它是當(dāng)前狀態(tài)的最優(yōu)解,而不取決于過去的決策,像是我們上面在遇到子數(shù)組的和為負(fù)數(shù)的情況下,我們采取的策略就是舍棄過去的子數(shù)組的和,而直接從該元素開始,這是當(dāng)前狀態(tài)下的最優(yōu)解,跟前面到底是舍棄還是直接從當(dāng)前元素開始的決策沒有任何影響。這就是無后效性。
?????動態(tài)規(guī)劃的"動態(tài)"就是來源于此:我們可以根據(jù)當(dāng)前的狀態(tài)動態(tài)的更換策略。
?????動態(tài)規(guī)劃在實現(xiàn)的過程中,為了解決冗余,會采取用空間換取時間的做法,也就是必須存儲該過程中的各種狀態(tài),這也就意味著它的空間復(fù)雜度要大于其他做法。這種情況就非常值得我們商榷了,所以我們在面試中必須確定一件事:我們是否可以使用輔助數(shù)組。像是上面的代碼,我們已經(jīng)將空間復(fù)雜度控制為O(1)了。
???? 關(guān)于上面那道題目的討論并沒有完,如果數(shù)組全為負(fù)數(shù)呢?
???? 上面的代碼對于這種情況的處理就是返回0,但也有可能會要求我們返回最大的負(fù)數(shù)或者其他情況,這些都需要和面試官商量好。
???? 動態(tài)規(guī)劃在數(shù)組問題中非常常見,像是下面這道題目:
題目六:寫一個時間復(fù)雜度盡可能低的程序,求一個數(shù)組中最長遞增子序列的長度。
?????我們還是先從一個測試用例開始著手:
?????假設(shè)一個數(shù)組為{1, -1, 2, -3, 4, -5, 6, 7},那么它的最長遞增子序列為{1, 2, 4, 6},那么它的長度就是4。
?????我們來看看這個過程:
?????當(dāng)i為0時,數(shù)組元素為1,所以最長遞增序列為{1};
???? 當(dāng)i為1時,數(shù)組元素為-1 < 1,所以舍棄之前的序列,重新建立序列:{-1};
???? 當(dāng)i為2時,數(shù)組元素為2時,由于2比1,-1都要大,所以現(xiàn)在的遞增序列為{1, 2}, {-1, 2};
???? ...
???? 我們可以看到,它滿足了無后效性,之前我們的策略所建立的序列并不會對我們現(xiàn)在的策略產(chǎn)生影響,所以我們可以利用動態(tài)規(guī)劃來求解。
?????使用動態(tài)規(guī)劃,最重要的是要確定各狀態(tài)下的函數(shù)表達(dá)式。假設(shè)在目標(biāo)數(shù)組data的前i個元素中,最長遞增子序列的長度為len[i - 1],那么:
???? len[i] = max{1, len[j] + 1}, 其中,data[i] > data[j],?j <= i - 1。
???? 只要data[i] > data[j],我們就可以將data[i]附加到前面i - 1個元素產(chǎn)生的遞增子序列后面。
???? 根據(jù)這樣的式子,我們可以這樣編碼:
int GetLengthOfSubArray(int* data, int length) {int[] len = new int[length];for(int i = 0; i < length; ++i){length[i] = 1;for(int j = 0; j < i; ++j){if(data[i] > data[j]){length[i] = len[j] + 1;}}}return Max(len); }???? 但是這個代碼的時間復(fù)雜度為O(N *?N + N) = O(N * N),我們還可以進(jìn)一步優(yōu)化:
int GetLengthOfSubArray(int* data, int length) {int[] MaxValue = new int[length + 1];MaxValue[1] = data[0];MaxValue[0] = Min(data) - 1;int[] len = new int[length];for(int i = 0; i < length; ++i){len[i] = 1;}int MaxLen = 1;for(int i = 1; i < length; ++i){int j;len[i] = BinarySearch(data[i], MaxValue, 0, i);
if(len[i] > MaxLen){MaxLen = len[i];MaxValue[len[i]] = data[i];}else if(MaxValue[j] < data[i]){MaxValue[j + 1] = data[i];}}return MaxLen; }
????? 現(xiàn)在我們的思路就是找出前面i - 1個元素的最長遞增序列,然后加上第i個元素,由于使用了二分查詢,所以整體的時間復(fù)雜度為O(N * log2N)。
??????動態(tài)規(guī)劃特別適合求子數(shù)組這種問題,因為該類問題的最優(yōu)解都需要用到輔助數(shù)組,而且都滿足無后效性。
?????
?????
????
????
????
????
?????
????
?
?????
轉(zhuǎn)載于:https://www.cnblogs.com/wenjiang/p/3307059.html
總結(jié)
以上是生活随笔為你收集整理的面试常备题----数组总结篇(上)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JAVA WEB开发环境搭建教程
- 下一篇: 数据下载工作笔记三:脚本