劍指Offer之楊氏矩陣

2021-09-01 21:17:34 字數 4248 閱讀 1739

即對於矩陣table有table[i][j] ≤table[i][j + 1], table[i][j] ≤ table[i + 1][j],我們也稱這樣的矩陣為楊氏矩陣。

給出判定某個數是否存在該矩陣中的高效演算法。

分析:

為了便於複雜度分析,我們暫時假定該矩陣為大小n*n。如下圖所示為乙個楊氏矩陣。

二分搜尋解法:

許多人都觀察到了矩陣在二維上都是有序的,所以使用在每一行(或者每一列)使用二分搜尋是很自然的想法。由於每一行二分搜尋需要o(lgn)時間,搜尋n行需要o(nlogn)的時間。顯然這個時間複雜度還是不夠高效。當然這只是第一步嘗試,不要讓自己過早的陷入到二分搜尋的泥潭中,好的方法還在後面。

一種錯誤的想法:

如果不細心也許會掉入乙個陷阱中。有人也許認為可以先從行來判定,如果某個數字於某2行間,則只需要檢查相應的2列即可,這是錯誤的。如下左邊圖所示,假定我們需要查詢9是否在矩陣中,由於9位於7到11之間,所以接下來在7和11的這兩列中(這2列在圖中高亮顯示)二分查詢9,雖然能夠查詢到9,雖然查詢9成功了,但是這個方法是錯誤的。因為10也位於7到11之間,但是10並不在這2列中。

即便是如下右邊圖示查詢範圍包括2行2列,儘管在查詢9和10都成功,但是還是錯誤的,反例大家可以自己找乙個。

step-wise線性搜尋解法:

從右上角開始,每次將搜尋值與右上角的值比較,如果大於右上角的值,則直接去除1行,否則,則去掉1列。如下圖顯示了查詢13的軌跡。首先與右上角15比較,13<15,所以去掉最右1列,然後與11比較,這是13>11,去掉最上面1行…以此類推,最後找到13。演算法複雜度o(n),最壞情況需要2n步,即從右上角開始查詢,而要查詢的目標值在左下角的時候。

}四分分解演算法:

通過觀察很容易發現該題可以使用分治法來解決。可以看到,矩陣的中間元素總是將矩陣分成了4個子矩陣。如下圖所示,中間元素9將矩陣分成了4個小矩陣,這4個小矩陣在行和列上面都是排好序的,所以原問題可以分解為4個子問題。進一步觀察可以發現,每次可以排除掉1個子矩陣,也就是說只要考慮3個子問題即可。如查詢目標元素為13,則13>9,因為左上角的子矩陣都小於9,所以左上角的子矩陣可以不用再查詢,只需要查詢剩下的3個子矩陣即可。同理,當查詢元素為6時,由於6<9,因為右下角的子矩陣都大於9,因此可以直接排除右下角的子矩陣,只需要查詢其他3個子矩陣即可。當然,如果中間元素等於查詢的目標元素,則直接返回即可,否則在剩下的3個子矩陣中查詢。

該演算法**如下,注意邊界條件,**中加粗的部分不可省略,否則會導致**不可終止。l==r&&u==d表示矩陣中只有乙個元素,此時若不等於目標元素target,則必須返回false。

[cpp]view plain

copy

bool

quadpart(

intmat[n_max],

intm,

intn,

inttarget,

intl,

intu,

intr,

intd,

int&targetrow,

int&targetcol)else

if(l==r&&u==d)

if(mat[row][col]>target)else

}bool

quadpart(

intmat[n_max],

intn,

inttarget,

int&row,

int&col)

該演算法複雜度是多少呢?可以通過公式計算:

原文公式:t(n) = 3t(n/2) + c,
t(

n) = 3t(

n/2) +

c,= 3 [ 3t(n/4) + c

] +c

= 3 [ 3 [ 3t(n/8)

+ c] + c

] +c

= 3k t(

n/2k

) +

c (3

k - 1)/2 = 3

k ( t(n/2

k) +

c ) - c/2setting

k = lg

n, t(

n) = 3

lg n

( t(1) +

c ) - c/2 =

o(3lg

n) = o(n

lg 3) <== 3

lg n = n

lg 3 = o(

n1.58)

注:我以為這裡公式應該是t(n) = 3 * t(n/4) + c ,不對的話請大家指正。
二分演算法

這個演算法我們從矩陣中間行或者中間列或者對角線開始查詢,找到s滿足

ai

< s

< a

i+1 , 其中ai為矩陣中的值。

a)從中間行查詢,如查詢10位於9到16之間。

b)從中間列查詢,如查詢10位於9到14之間。

c)從對角線查詢,查詢10位於9到17之間。

顯然不管從**查詢,都可以將原矩陣分成2個子矩陣,這樣就分成了2個子問題,比起四分分解法的3個子問題,這個方法應該更好。

**如下,注意這裡**確定範圍用的是線性查詢,實際可以通過二分查詢加速整個過程。

[cpp]view plain

copy

bool

binpart(

intmat[n_max],

intm,

intn,

inttarget,

intl,

intu,

intr,

intd,

int&targetrow,

int&targetcol)

row++;

}return

binpart(mat,m,n,target,mid+1,u,r,row-1,targetrow,targetcol)||

binpart(mat,m,n,target,l,row,mid-1,d,targetrow,targetcol);

}bool

binpart(

intmat[n_max],

intn,

inttarget,

int&row,

int&col)

該方法複雜度的分析:為了方便,假定最後查詢的子矩陣為分成了2個相同大小的子矩陣,且都為原來1/4大小。

t(n)=2t(n/4)+cn

如果採用二分查詢確定範圍,則t(n)=2t(n/4)+clgn

劍指offer 矩陣覆蓋

我們可以用2 1的小矩形橫著或者豎著去覆蓋更大的矩形。請問用n個2 1的小矩形無重疊地覆蓋乙個2 n的大矩形,總共有多少種方法?使用dp,當n時,選著豎著放乙個,那麼後面的可能性為f n 1 選擇橫著放乙個,那麼要佔兩個位置,後面的可能性為f n 2 故f n f n 1 f n 2 f 1 1,f...

劍指offer 矩陣覆蓋

題目 矩陣覆蓋 題目描述 我們可以用2 1的小矩形橫著或者豎著去覆蓋更大的矩形。請問用n個2 1的小矩形無重疊地覆蓋乙個2 n的大矩形,總共有多少種方法?思路 此題和前面裴波那切類題是一樣的,尤其是和那個上樓梯的題 找到遞推式 f n f n 1 f n 2 n 2 對於這類題的變形 相應的結論應該...

劍指Offer之之字行列印矩陣

這道題的關鍵是將陣列的列印分為多個對角線的列印,在對角線的列印是雙向的 且用乙個boolean型別的變數用來控制是從上到下,還是從下到上 答應對角線上的元素,可以從右上到左下,up為true,從左下到右上,false為左下到右上 給定乙個右上角的點和左下角的點 public static void ...