題目:求一個連通圖的割點,割點的定義是,若是除去此節點和與其相關的邊,圖再也不連通,描述算法。html
分析:ios
1. 最簡單也是最直接的算法是,刪除一個點而後判斷連通性,若是刪除此點,圖再也不連通,則此點是割點,反之不是割點(圖的連通性通常經過深搜來斷定,是否能一次搜索完 所有頂點);面試
2. 經過深搜優先生成樹來斷定。從任一點出發深度優先遍歷獲得優先生成樹,對於樹中任一頂點V而言,其孩子節點爲鄰接點。由深度優先生成樹可得出兩類割點的特性:算法
(1)若生成樹的根有兩棵或兩棵以上的子樹,則此根頂點必爲割點。由於圖中不存在鏈接不一樣子樹頂點的邊,若刪除此節點,則樹便成爲森林;數組
(2)若生成樹中某個非葉子頂點V,其某棵子樹的根和子樹中的其餘節點均沒有指向V的祖先的回邊,則V爲割點。由於刪去v,則其子樹和圖的其它部分被分割開來。數據結構
仍然利用深搜算法,只不過在這裏定義visited[v]表示爲深度優先搜索遍歷圖時訪問頂點v的次序號,定義low[v]=Min{visited[v],low[w],visited[k]},其中w是頂點v在深度優先生成樹上的孩子節點;k是頂點v在深度優先生成樹上由回邊聯結的祖先節點。less
割點斷定條件:若是對於某個頂點v,存在孩子節點w且low[w]>=visited[v],則該頂點v必爲關節點。由於當w是v的孩子節點時,low[w]>=visited[v],代表w及其子孫均無指向v的祖先的回邊,那麼當刪除頂點v後,v的孩子節點將於其餘節點被分割開來,歷來造成新的連通份量。ide
#include <iostream>
#include <string> using namespace std; #define MAX_VERTEX_NUM 13 //鄰接表存儲結構 typedef struct ArcNode{ int adjvex; ArcNode *nextarc; }ArcNode; typedef struct VNode{ string data; ArcNode* firstarc; }VNode,AdjList[MAX_VERTEX_NUM]; typedef struct{ AdjList vertices; int vexnum, arcnum; }ALGraph; //返回u在圖中的位置 int LocateVex(ALGraph G, string u) { for(int i=0; i<G.vexnum; i++) if(G.vertices[i].data==u) return i; return -1; } //構造圖 void CreateDG(ALGraph &G) { string v1, v2; int i, j, k; cout<<"請輸入頂點數和邊數:"; cin>>G.vexnum>>G.arcnum; cout<<"請輸入頂點:"; for(i=0; i<G.vexnum; i++) { cin>>G.vertices[i].data; G.vertices[i].firstarc=NULL; } cout<<"請輸入邊:"<<endl; for(k=0; k<G.arcnum; k++) { cin>>v1>>v2; i=LocateVex(G, v1); j=LocateVex(G, v2); //無向圖 ArcNode *arc=new ArcNode; arc->adjvex=j; arc->nextarc=G.vertices[i].firstarc; G.vertices[i].firstarc=arc; arc=new ArcNode; arc->adjvex=i; arc->nextarc=G.vertices[j].firstarc; G.vertices[j].firstarc=arc; } } //求割點 int count ; int visited[MAX_VERTEX_NUM]; int low[MAX_VERTEX_NUM]; //從第v0個頂點出發深搜,查找並輸出關節點(割點) void DFSArticul(ALGraph G, int v0) { int min, w; ArcNode *p; visited[v0]=min=++count;//v0是第count個訪問的頂點,min的初值爲visited[v0],即v0的訪問次序 for(p=G.vertices[v0].firstarc; p ; p=p->nextarc) { w=p->adjvex; if(visited[w]==0)//w不曾訪問,是v0的孩子 { DFSArticul(G, w);//從第w個頂點出發深搜,查找並輸出關節點(割點),返回前求得low[w] if(low[w]<min)//若是v0的孩子節點w的low[]小,說明孩子節點還與其餘節點(祖先)相鄰 min=low[w]; if(low[w]>=visited[v0])//v0的孩子節點w只與v0相連,則v0是關節點(割點) cout<<G.vertices[v0].data<<" "; } else if(visited[w]<min)//w已訪問,則w是v0生成樹上祖先,它的訪問順序必小於min min=visited[w]; } low[v0]=min;//low[v0]取三者最小值 } void FindArticul(ALGraph G) { int i, v; ArcNode *p; count=1; visited[0]=1;//從0號節點開始 for(i=1; i<G.vexnum; i++) visited[i]=0; p=G.vertices[0].firstarc; v=p->adjvex; DFSArticul(G, v); if(count<G.vexnum) { cout<<G.vertices[0].data<<" "; while(p->nextarc) { p=p->nextarc; v=p->adjvex; if(visited[v]==0) DFSArticul(G, v); } } } void main() { ALGraph g; CreateDG(g); cout<<"割點以下: "<<endl; FindArticul(g); cout<<endl; }
題目描述
輸入n個整數,輸出其中最小的k個。函數
分析與解法
解法一
要求一個序列中最小的k個數,按照慣有的思惟方式,則是先對這個序列從小到大排序,而後輸出前面的最小的k個數。post
至於選取什麼的排序方法,我想你可能會第一時間想到快速排序(咱們知道,快速排序平均所費時間爲 n*logn
),而後再遍歷序列中前k個元素輸出便可。所以,總的時間複雜度: O(n * log n)+O(k)=O(n * log n)
。
解法二
我們再進一步想一想,題目沒有要求最小的k個數有序,也沒要求最後n-k個數有序。既然如此,就沒有必要對全部元素進行排序。這時,我們想到了用選擇或交換排序,即:
一、遍歷n個數,把最早遍歷到的k個數存入到大小爲k的數組中,假設它們便是最小的k個數;
二、對這k個數,利用選擇或交換排序找到這k個元素中的最大值kmax(找最大值須要遍歷這k個數,時間複雜度爲 O(k)
);
三、繼續遍歷剩餘n-k個數。假設每一次遍歷到的新的元素的值爲x,把x與kmax比較:若是 x < kmax
,用x替換kmax,並回到第二步從新找出k個元素的數組中最大元素kmax‘;若是 x >= kmax
,則繼續遍歷不更新數組。
每次遍歷,更新或不更新數組的所用的時間爲 O(k)
或 O(0)
。故整趟下來,時間複雜度爲 n*O(k)=O(n*k)
。
解法三
更好的辦法是維護容量爲k的最大堆,原理跟解法二的方法類似:
- 一、用容量爲k的最大堆存儲最早遍歷到的k個數,一樣假設它們便是最小的k個數;
- 二、堆中元素是有序的,令k1<k2<...<kmax(kmax設爲最大堆中的最大元素)
- 三、遍歷剩餘n-k個數。假設每一次遍歷到的新的元素的值爲x,把x與堆頂元素kmax比較:若是
x < kmax
,用x替換kmax,而後更新堆(用時logk);不然不更新堆。
這樣下來,總的時間複雜度: O(k+(n-k)*logk)=O(n*logk)
。此方法得益於堆中進行查找和更新的時間複雜度均爲: O(logk)
(若使用解法二:在數組中找出最大元素,時間複雜度: O(k))
。
解法四
在《數據結構與算法分析--c語言描述》一書,第7章第7.7.6節中,闡述了一種在平均狀況下,時間複雜度爲 O(N)
的快速選擇算法。以下述文字:
Since we can sort the file in O(nlog n) time, one might expect to obtain a better time bound for selection. The algorithm we present to find the kth smallest element in a set S is almost identical to quicksort. In fact, the first three steps are the same. We will call this algorithm quickselect(叫作快速選擇). Let |Si| denote the number of elements in Si(令|Si|爲Si中元素的個數). The steps of quickselect are( 步驟以下 ):
- If |S| = 1, then k = 1 and return the elements in S as the answer. If a cutoff for small files is being used and |S| <=CUTOFF, then sort S and return the kth smallest element.
- Pick a pivot element, v (- S.(選取S中一個元素做爲樞紐元v)
- Partition S - {v} into S1 and S2, as was done with quicksort. (將集合S-{v}分割成S1和S2,就像快速排序那樣)
- If k <= |S1|, then the kth smallest element must be in S1. In this case, return quickselect (S1, k). If k = 1 + |S1|, then the pivot is the kth smallest element and we can return it as the answer. Otherwise, the kth smallest element lies in S2, and it is the (k - |S1| - 1)st smallest element in S2. We make a recursive call and return quickselect (S2, k - |S1| - 1). (若是k<=|S1|,那麼第k個最小元素必然在S1中。在這種狀況下,返回quickselect(S1,k)。若是k=1+|S1|,那麼樞紐元素就是第k個最小元素,即找到,直接返回它。不然,這第k個最小元素就在S2中,即S2中的第(k-|S1|-1)個最小元素,咱們遞歸調用並返回quickselect(S2,k-|S1|-1))。
In contrast to quicksort, quickselect makes only one recursive call instead of two. The worst case of quickselect is identical to that of quicksort and is O(n2). Intuitively, this is because quicksort's worst case is when one of S1 and S2 is empty; thus, quickselect(快速選擇) is not really saving a recursive call. The average running time, however, is O(n)( 不過,其平均運行時間爲O(N) ). The analysis is similar to quicksort's and is left as an exercise.
示例代碼:
//QuickSelect 將第k小的元素放在 a[k-1]
void QuickSelect( int a[], int k, int left, int right ) { int i, j; int pivot; if( left + cutoff <= right ) { pivot = median3( a, left, right ); //取三數中值做爲樞紐元,能夠很大程度上避免最壞狀況 i = left; j = right - 1; for( ; ; ) { while( a[ ++i ] < pivot ){ } while( a[ --j ] > pivot ){ } if( i < j ) swap( &a[ i ], &a[ j ] ); else break; } //重置樞紐元 swap( &a[ i ], &a[ right - 1 ] ); if( k <= i ) QuickSelect( a, k, left, i - 1 ); else if( k > i + 1 ) QuickSelect( a, k, i + 1, right ); } else InsertSort( a + left, right - left + 1 ); }
這個快速選擇SELECT算法,相似快速排序的劃分方法。N個數存儲在數組S中,再從數組中選取「中位數的中位數」做爲樞紐元X,把數組劃分爲Sa和Sb倆部分,Sa<=X<=Sb,若是要查找的k個元素小於Sa的元素個數,則返回Sa中較小的k個元素,不然返回Sa中全部元素+Sb中小的k-|Sa|個元素,這種解法在平均狀況下能作到 O(N)
的複雜度。
解法五
《算法導論》介紹了一個隨機選取主元的選擇算法RANDOMIZED-SELECT。它每次都是隨機選取數列中的一個元素做爲主元,在 O(n)
的時間內找到第k小的元素,而後遍歷輸出前面的k個小的元素。平均時間複雜度: O(n+k)=O(n)
(當k比較小時)。
咱們知道,快速排序是以固定的第一個或最後一個元素做爲主元,每次遞歸劃分都是不均等的,最後的平均時間複雜度爲: O(n*logn)
。而RANDOMIZED-SELECT與普通的快速排序不一樣,它每次遞歸都是隨機選擇序列,從第一個到最後一個元素中任一一個做爲主元。
下面是RANDOMIZED-SELECT(A, p, r)完整僞碼:
PARTITION(A, p, r) //partition過程 p爲第一個數,r爲最後一個數 1 x ← A[r] //以最後一個元素做爲主元 2 i ← p - 1 3 for j ← p to r - 1 4 do if A[j] ≤ x 5 then i ← i + 1 6 exchange A[i] <-> A[j] 7 exchange A[i + 1] <-> A[r] 8 return i + 1 RANDOMIZED-PARTITION(A, p, r) //隨機快排的partition過程 1 i ← RANDOM(p, r) //i 隨機取p到r中個一個值 2 exchange A[r] <-> A[i] //以隨機的 i做爲主元 3 return PARTITION(A, p, r) //調用上述原來的partition過程 RANDOMIZED-SELECT(A, p, r, i) //以線性時間作選擇,目的是返回數組A[p..r]中的第i 小的元素 1 if p = r //p=r,序列中只有一個元素 2 then return A[p] 3 q ← RANDOMIZED-PARTITION(A, p, r) //隨機選取的元素q做爲主元 4 k ← q - p + 1 //k表示子數組 A[p…q]內的元素個數,處於劃分低區的元素個數加上一個主元元素 5 if i == k //檢查要查找的i 等於子數組中A[p....q]中的元素個數k 6 then return A[q] //則直接返回A[q] 7 else if i < k 8 then return RANDOMIZED-SELECT(A, p, q - 1, i) //獲得的k 大於要查找的i 的大小,則遞歸到低區間A[p,q-1]中去查找 9 else return RANDOMIZED-SELECT(A, q + 1, r, i - k) //獲得的k 小於要查找的i 的大小,則遞歸到高區間A[q+1,r]中去查找。
下面則是《算法導論》原版關於RANDOMIZED-SELECT(A, p, r)爲 O(n)
的證實,闡述以下:
此RANDOMIZED-SELECT最壞狀況下時間複雜度爲Θ(n2),即便是要選擇最小元素也是如此,由於在劃分時可能極不走運,老是按餘下元素中的最大元素進行劃分,而劃分操做須要O(n)的時間。
然而此算法的平均狀況性能極好,由於它是隨機化的,故沒有哪種特別的輸入會致使其最壞狀況的發生。
算法導論上,針對此 RANDOMIZED-SELECT算法平均時間複雜度爲O(n)的證實 ,引用以下,或許,能給你我多點的啓示(原本想直接引用第二版中文版的翻譯文字,但在中英文對照閱讀的狀況下,發現第二版中文版的翻譯實在不怎麼樣,因此,得本身一個一個字的敲,最終敲完修正以下),分4步證實:
-
當RANDOMIZED-SELECT做用於一個含有n個元素的輸入數組A[p ..r]上時,所需時間是一個隨機變量,記爲T(n),咱們能夠這樣獲得線性指望值E [T(n)]的下界:程序RANDOMIZED-PARTITION會以等同的可能性返回數組中任何一個元素爲主元,所以,對於每個k,(1 ≤k ≤n),子數組A[p ..q]有k個元素,它們所有小於或等於主元元素的機率爲1/n.對k = 1, 2,...,n,咱們定指示器X k ,爲:
X k = I{子數組A[p ..q]恰有k個元素} ,
咱們假定元素的值不一樣,所以有
E[X k ]=1/n
當調用RANDOMIZED-SELECT而且選擇A[q]做爲主元元素的時候,咱們事先不知道是否會當即找到咱們所想要的第i小的元素,由於,咱們頗有可能須要在子數組A[p ..q - 1], 或A[q + 1 ..r]上遞歸繼續進行尋找.具體在哪個子數組上遞歸尋找,視第i小的元素與A[q]的相對位置而定.
-
假設T(n)是單調遞增的,咱們能夠將遞歸所需時間的界限限定在輸入數組時可能輸入的所需遞歸調用的最大時間(此句話,原中文版的翻譯也是有問題的).換言之,咱們判定,爲獲得一個上界,咱們假定第i小的元素老是在劃分的較大的一邊,對一個給定的RANDOMIZED-SELECT,指示器Xk恰好在一個k值上取1,在其它的k值時,都是取0.當Xk =1時,可能要遞歸處理的倆個子數組的大小分別爲k-1,和n-k,所以可獲得遞歸式爲
取指望值爲:
爲了能應用等式 (C.23) ,咱們依賴於X k 和T(max(k - 1,n - k))是獨立的隨機變量(這個能夠證實,證實此處略)。
3. 下面,咱們來考慮下表達式max(k - 1,n -k)的結果.咱們有:
若是n是偶數,從T(⌉)到T(n - 1)每一個項在總和中恰好出現倆次,T(⌋)出現一次。所以,有
咱們能夠用替換法來解上面的遞歸式。假設對知足這個遞歸式初始條件的某個常數c,有T(n) ≤cn。咱們假設對於小於某個常數c(稍後再來講明如何選取這個常數)的n,有T(n) =O(1)。 同時,還要選擇一個常數a,使得對於全部的n>0,由上式中O(n)項(用來描述這個算法的運行時間中非遞歸的部分)所描述的函數,可由an從上方限界獲得(這裏,原中文版的翻譯的確是有點含糊)。利用這個概括假設,能夠獲得:
- 爲了完成證實,還須要證實對足夠大的n,上面最後一個表達式最大爲cn,即要證實:cn/4 -c/2 -an ≥ 0.若是在倆邊加上c/2,而且提取因子n,就能夠獲得n(c/4 -a) ≥c/2.只要咱們選擇的常數c能知足c/4 -a > 0, i.e.,即c > 4a,咱們就能夠將倆邊同時除以c/4 -a, 最終獲得:
綜上,若是假設對n < 2c/(c -4a),有T(n) =O(1),咱們就能獲得E[T(n)] =O(n)。因此,最終咱們能夠得出這樣的結論,並確認無疑: 在平均狀況下,任何順序統計量(特別是中位數)均可以在線性時間內獲得 。
結論:RANDOMIZED-SELECT的時間複雜度爲 O(N)
,但它在最壞狀況下時間的複雜度爲 O(N^2)
。
解法五
《算法導論》第九章第9.3節介紹了一個最壞狀況線性時間的選擇算法,以下:
9.3 Selection in worst-case linear time( 最壞狀況下線性時間的選擇算法 )
We now examine a selection algorithm whose running time isO(n) in the worst case(如今來看,一個最壞狀況運行時間爲O(N)的選擇算法SELECT). Like RANDOMIZED-SELECT, the algorithm SELECT finds the desired element by recursively partitioning the input array. The idea behind the algorithm, however, is toguarantee a good split when the array is partitioned. SELECT uses the deterministic partitioning algorithm PARTITION from quicksort (seeSection 7.1), modified to take the element to partition around as an input parameter(像RANDOMIZED-SELECT同樣,SELECTT經過輸入數組的遞歸劃分來找出所求元素,可是,該算法的基本思想是要保證對數組的劃分是個好的劃分。SECLECT採用了取自快速排序的肯定性劃分算法partition,並作了修改,把劃分主元元素做爲其參數).
The SELECT algorithm determines theith smallest of an input array ofn > 1 elements by executing the following steps. (Ifn = 1, then SELECT merely returns its only input value as theith smallest.)(算法SELECT經過執行下列步驟來肯定一個有n>1個元素的輸入數組中的第i小的元素。(若是n=1,則SELECT返回它的惟一輸入數值做爲第i個最小值。))
- Divide then elements of the input array into⌋ groups of 5 elements each and at most one group made up of the remainingn mod 5 elements.
- Find the median of each of the⌉ groups by first insertion sorting the elements of each group (of which there are at most 5) and then picking the median from the sorted list of group elements.
- Use SELECT recursively to find the medianx of the⌉ medians found in step 2. (If there are an even number of medians, then by our convention,x is the lower median.)
- Partition the input array around the median-of-mediansx using the modified version of PARTITION. Letk be one more than the number of elements on the low side of the partition, so thatx is thekth smallest element and there aren-k elements on the high side of the partition.(利用修改過的partition過程,按中位數的中位數x對輸入數組進行劃分,讓k比劃低去的元素數目多1,因此,x是第k小的元素,而且有n-k個元素在劃分的高區)
- Ifi =k, then returnx. Otherwise, use SELECT recursively to find theith smallest element on the low side ifi k.( 若是要找的第i小的元素等於程序返回的k ,即i=k,則返回x。不然,若是ik,則在高區間找第(i-k)個最小元素)
(以上五個步驟,即本文上面的第四節末中所提到的所謂「五分化中項的中項」的方法。)
To analyze the running time of SELECT, we first determine a lower bound on the number of elements that are greater than the partitioning element x. (爲了分析SELECT的運行時間,先來肯定大於劃分主元元素x的的元素數的一個下界)Figure 9.1 is helpful in visualizing this bookkeeping. At least half of the medians found in step 2 are greater than[1] the median-of-medians x. Thus, at least half of the ⌉ groupscontribute 3 elements that are greater than x, except for the one group that has fewer than 5 elements if 5 does not dividen exactly, and the one group containingx itself. Discounting these two groups, it follows that the number of elements greater thanx is at least:
(Figure 9.1: 對上圖的解釋或稱對SELECT算法的分析:n個元素由小圓圈來表示,而且每個組佔一縱列。組的中位數用白色表示,而各中位數的中位數x也被標出。(當尋找偶數數目元素的中位數時,使用下中位數)。箭頭從比較大的元素指向較小的元素,從中能夠看出,在x的右邊,每個包含5個元素的組中都有3個元素大於x,在x的左邊,每個包含5個元素的組中有3個元素小於x。大於x的元素以陰影背景表示。 )
Similarly, the number of elements that are less thanx is at least 3n/10 - 6. Thus, in the worst case, SELECT is called recursively on at most 7n/10 + 6 elements in step 5.
We can now develop a recurrence for the worst-case running timeT(n) of the algorithm SELECT. Steps 1, 2, and 4 take O(n) time. (Step 2 consists ofO(n) calls of insertion sort on sets of sizeO(1).) Step 3 takes timeT(⌉), and step 5 takes time at mostT(7n/10+ 6), assuming thatT is monotonically increasing. We make the assumption, which seems unmotivated at first, that any input of 140 or fewer elements requiresO(1) time; the origin of the magic constant 140 will be clear shortly. We can therefore obtain the recurrence:
We show that the running time is linear by substitution. More specifically, we will show thatT(n) ≤cn for some suitably large constant c and alln > 0. We begin by assuming thatT(n) ≤cn for some suitably large constantc and alln ≤ 140; this assumption holds ifc is large enough. We also pick a constanta such that the function described by theO(n) term above (which describes the non-recursive component of the running time of the algorithm) is bounded above byan for alln > 0. Substituting this inductive hypothesis into the right-hand side of the recurrence yields
T(n) ≤ c⌉ +c(7n/10 + 6) +an
≤ cn/5 +c + 7cn/10 + 6c +an
= 9cn/10 + 7c +an
= cn + (-cn/10 + 7c +an) ,
which is at mostcn if
Inequality (9.2) is equivalent to the inequalityc ≥ 10a(n/(n - 70)) when n > 70. Because we assume thatn ≥ 140, we have n/(n - 70) ≤ 2, and so choosing c ≥ 20a will satisfyinequality (9.2). (Note that there is nothing special about the constant 140; we could replace it by any integer strictly greater than 70 and then choosec accordingly.) The worst-case running time of SELECT is therefore linear( 所以,此SELECT的最壞狀況的運行時間是線性的 ).
As in a comparison sort (seeSection 8.1), SELECT and RANDOMIZED-SELECT determine information about the relative order of elements only by comparing elements. Recall fromChapter 8 that sorting requiresΩ(n lgn) time in the comparison model, even on average (see Problem 8-1). The linear-time sorting algorithms in Chapter 8 make assumptions about the input. In contrast, the linear-time selection algorithms in this chapter do not require any assumptions about the input. They are not subject to the Ω(n lgn) lower bound because they manage to solve the selection problem without sorting.
(與比較排序(算法導論8.1節)中的同樣,SELECT和RANDOMIZED-SELECT僅經過元素間的比較來肯定它們之間的相對次序。在算法導論第8章中,咱們知道在比較模型中,即便在平均狀況下,排序仍然要 O(n*logn)
的時間。第8章得線性時間排序算法在輸入上作了假設。 相反地,本節提到的此相似partition過程的SELECT算法不須要關於輸入的任何假設,它們不受下界 O(n*logn)
的約束,由於它們沒有使用排序就解決了選擇問題 (看到了沒,道出了此算法的本質阿))
Thus, the running time is linear because these algorithms do not sort; the linear-time behavior is not a result of assumptions about the input, as was the case for the sorting algorithms inChapter 8. Sorting requiresΩ(n lgn) time in the comparison model, even on average (see Problem 8-1), and thus the method of sorting and indexing presented in the introduction to this chapter is asymptotically inefficient.(因此,本節中的選擇算法之因此具備線性運行時間,是由於這些算法沒有進行排序;線性時間的結論並不須要在輸入上所任何假設,便可獲得)。
觸類旁通
一、谷歌面試題:輸入是兩個整數數組,他們任意兩個數的和又能夠組成一個數組,求這個和中前k個數怎麼作?
分析:
「假設兩個整數數組爲A和B,各有N個元素,任意兩個數的和組成的數組C有N^2個元素。
那麼能夠把這些和當作N個有序數列:
A[1]+B[1] <= A[1]+B[2] <= A[1]+B[3] <=…
A[2]+B[1] <= A[2]+B[2] <= A[2]+B[3] <=…
…
A[N]+B[1] <= A[N]+B[2] <= A[N]+B[3] <=…
問題轉變成,在這N^2個有序數列裏,找到前k小的元素」
二、有兩個序列A和B,A=(a1,a2,...,ak),B=(b1,b2,...,bk),A和B都按升序排列。對於1<=i,j<=k,求k個最小的(ai+bj)。要求算法儘可能高效。
三、給定一個數列a1,a2,a3,...,an和m個三元組表示的查詢,對於每一個查詢(i,j,k),輸出ai,ai+1,...,aj的升序排列中第k個數。