前言
先說一些題外的東西吧。受到春躍大神的影響和啟發,推薦了這個算法公開課給我,晚上睡覺前點開一看發現課還有兩天要開始,本着要好好系統地學習一下算法,於是就爬起來拉上兩個小伙伴組團報名了。今天聽了第一節課,說真的很實用,特別是對於我這種算法不扎實,並且又想找工作,提高自己的情況。 那就不多說廢話了,以后每周都寫個總結吧,就趁着這一個月好好把算法提高一下。具體就從:課堂筆記、leetcode和lintcode相關習題、hdu和poj相關習題三個方面來寫吧。希望自己能夠堅持下來,給大家分享一些好的東西。
outline:
- 第一境界:會寫程序
- Find First Position of Target
- Find Last Position of Target
- 第二境界:找到第一個/最后一個滿足某個條件的位置/值
- Search a 2D Matrix
- Find Minimum in Rotated Sorted Array
- 第三境界:保留有解的那一半
- Find Peak Element
課堂筆記
二分查找這類題以前接觸的也算是比較多的了,所以還算相對熟悉,但今天聽老師講過以后,還是覺得有了很多新的認識,最有印象的就是令狐老師講的三個境界:
1. 第一境界:會寫程序
這個境界我自認為在刷了那么多leetcode之后算是沒有問題的了,套了不少模版,雖然還有一些邊界問題考慮不周全,但是經過調試,應該沒有什么問題,經過幾次面試,也面到過Binary Seach。想必大家也有很好的基礎。 正如老師說的,這個境界還是存在一些問題,比如解決二分程序的三大痛點、權衡遞歸與非遞歸。 對於第一個問題,其實就是start和end的位置選取,比如容易進入死循環,或者容易分不清楚到底應該是start = mid還是start = mid+ 1等。以下給出一個代碼模版,這個也是我之前寫二分問題經常會寫的樣子:
int start = 0, end = nums.size() - 1; while (start < end){ int mid = (start + end)/2; if (...) {...} else if (...) {...} else {...} }
想必大家都會把循環條件寫成start < end或者start <= end這樣的,這樣在一些情況下也確實沒有問題(這里直接上一個題):
Find First Position of Target
http://www.lintcode.com/zh-cn/problem/first-position-of-target/
給定一個排序的整數數組(升序)和一個要查找的整數
target
,用O(logn)
的時間查找到target第一次出現的下標(從0開始),如果target不存在於數組中,返回-1
。樣例
在數組
[1, 2, 3, 3, 4, 5, 10]
中二分查找3
,返回2
。
這個題應該是Binary Search最基礎的題,直接套用模版就可以,以下是這個題的代碼(Bug Free):
int binarySearch(vector<int> &array, int target) { if (!array.size()) return -1; int start = 0, end = array.size() - 1; while (start < end) { int mid = (start + end) >> 1; if (array[mid] < target) { start = mid + 1; } else if (array[mid] > target) { end = mid -1; } else { end = mid; } } if (array[start] == target) return start; return -1; }
因為比較簡單,就不再多說了,這里需要注意的幾個點是,int mid = (start + end) >>1;其實就是int mid = (start + end)/2;因為在面試中如果會位運算的話,還是能夠給面試官留下很好的印象。有的人說直接加起來除以2會溢出,其實start和end不會大到超過int的最大值的,因為一個vector也不會去開辟那么大的空間,但是寫成`int mid = (end - start)/2 + start;`也能顯得你比較不錯。綜上,兩種方法都可以。 在這個題中因為是找第一個與target相等的值,所以用這種方法不會出問題,但是在考慮下面的題,就會出現問題:
Find Last Position of Target
http://www.lintcode.com/zh-cn/problem/last-position-of-target/
給一個升序數組,找到target最后一次出現的位置,如果沒出現過返回
-1
樣例
給出 [1, 2, 2, 4, 5, 5].
target =
2
, 返回2
.target =
5
, 返回5
.target =
6
, 返回-1
.
錯誤代碼如下:
while (start < end) { int mid = ( start + end ) >>1; if (A[mid] < target) {
start = mid + 1;
} else if ( A[mid] > target) {
end = mid -1;
} else {
start = mid;
} }
這里如果這樣寫的話,代碼就會進入死循環,因為在求mid的時候是向左邊取整的。考慮這樣的一個情況[...,5,5],假設target為5,那么start就會一直向右靠近,最后到n-2的位置,而end此時為n-1,再次進入循環mid等於n-2,所以就進入了死循環。 根據課上老師所說的,建議大家寫成start + 1 < end,最后再判斷start和end(按照所需先后判斷)即可,這種寫法適用於所有的情況,不容易出現問題。 ps. 這里把條件寫成如下也可行:
while (start + 1 < end) { int mid = (start + end)>>1; if (A[mid] > target) {
end = mid; } else {
start = mid;
} }
因為start和end不管是否包括mid值都不影響最后的結果。 這個境界需要理解一個重點: 二分法實際上就是把區間變小的問題,把一個長度為n的區間變為n/2,然后再變小,即:
T(n) = T(n/2) + O(1) = O(logn)
通過O(1)的時間,把規模為n的問題變為n/2 當面試的時候,有O(n)的解,如果面試官需要你進一步優化,那么很大可能就是需要用二分O(logn)的方法來做。 實際上的步驟:
**區間縮小-> 剩下兩個下標->判斷兩個下標**
**注:不要把縮小區間和得到答案放在一個循環里面,容易出問題,增加難度**
2. 第二境界:找到第一個/最后一個滿足某個條件的位置/值
寫出一個高效的算法來搜索 m × n矩陣中的值。
這個矩陣具有以下特性:
- 每行中的整數從左到右是排序的。
- 每行的第一個數大於上一行的最后一個整數。
樣例
考慮下列矩陣:
[ [1, 3, 5, 7], [10, 11, 16, 20], [23, 30, 34, 50] ]
給出
target = 3
,返回true
bool searchMatrix(vector<vector<int> > &matrix, int target) { if (!matrix.size()||!matrix[0].size()) return false; int start = 0, end = matrix.size() - 1; while (start + 1 < end) { int mid = (end - start)/2 + start; if (matrix[mid][0] < target) start = mid; else end = mid; } int new_start = 0,new_end = matrix[0].size()-1; int index = matrix[end][0] <= target ?end:start; while (new_start + 1 < new_end) { int mid = (new_end - new_start)/2 + new_start; if (matrix[index][mid] > target) new_end = mid; else if (matrix[index][mid] < target) new_start = mid; else return true; } if (matrix[index][new_end] == target) return true; if (matrix[index][new_start] == target) return true; return false; }
bool searchMatrix(vector<vector<int> > &matrix, int target) { if (!matrix.size()||!matrix[0].size()) return false; int m = matrix.size(); int n = matrix[0].size(); int start = 0, end = n * m - 1; while (start + 1 < end) { int mid = (end - start)/2 + start; int x = mid / n; int y = mid % n; if (matrix[x][y] > target) { end = mid; } else { start = mid; } } int x = start / n; int y = start % n; if (matrix[x][y] == target) { return true; } x = end / n; y = end % n; if (matrix[x][y] == target) { return true; } return false; }
假設一個旋轉排序的數組其起始位置是未知的(比如0 1 2 4 5 6 7 可能變成是4 5 6 7 0 1 2)。
你需要找到其中最小的元素。
你可以假設數組中不存在重復的元素。
樣例
給出[4,5,6,7,0,1,2] 返回 0
int findMin(vector<int> &num) { if (!num.size()) return 0; int start = 0, end = num.size() - 1; int target = num[end]; while (start + 1 < end) { int mid = (end - start)/2 + start; if (num[mid] <= target) { end = mid; } else { start = mid; } } if (num[start] <= target) { return num[start]; } else { return num[end]; } }
給出一個整數數組(size為n),其具有以下特點:
- 相鄰位置的數字是不同的
- A[0] < A[1] 並且 A[n - 2] > A[n - 1]
假定P是峰值的位置則滿足
樣例A[P] > A[P-1]
且A[P] > A[P+1]
,返回數組中任意一個峰值的位置。給出數組
[1, 2, 1, 3, 4, 5, 7, 6]
返回1
, 即數值 2 所在位置, 或者6
, 即數值 7 所在位置.

第一種情況:當前點就是峰值,直接返回當前值。
第二種情況:當前點是谷點,不論往那邊走都可以找到峰值。
第三種情況:當前點處於下降的中間,往左邊走可以到達峰值。
第四種情況:當前點處於上升的中間,往右邊走可以達到峰值。
分析了四種情況,那么就容易把有答案的一半保留下來了,接下來就判斷是否能夠找到峰值即可。代碼如下(Bug Free):
int findPeak(vector<int> A) { if (!A.size()) return 0; int start = 0; int end = A.size() -1; while (start + 1 < end) { int mid = (end - start)/2 + start; if (A[mid] > A[mid - 1] && A[mid] > A[mid + 1]) { return mid; } else if (A[mid] <= A[mid+1] && A[mid] >= A[mid -1]) { start = mid; } else if (A[mid] >= A[mid+1] && A[mid] <= A[mid -1]) { end = mid; } else { start = mid; } } if (start >= 1 && A[start] > A[start - 1] && A[start] > A[start + 1]) return start;
if (end <= A.size()-2 && A[end] > A[end-1] && A[end] > A[end+1]) return end; }
這道題的難點其實就是把各種情況考慮一下,然后把有答案的部分保留下來,基本上就沒有問題了。
總結
本文只是挑選了一些比較好的課上的題進行了講解,還有部分題沒有寫出來,也會在后續的博客中。
對於我個人而言,二分法算是比較熟悉的一個方法,之前在做微軟校招第一題的時候用的就是二分的方法。在面試中也是比較常用到的一種方法,因為總有那么一種說法嘛:比0(n)還要快的算法復雜度,那必須就是0(logn)了(這里說的是在一般的面試情況下)那么O(logn)就必然要考慮二分的方法來做了。一般都會與一些排序的序列、在一段有規則的序列等情況中找到符合某個條件的位置/值。這個模塊還是需要多練習,然后就能夠很好上手了,如果想要能夠在算法面試中有更好的突破,還是需要去解決一些難一點的題,諸如poj或者hdu這樣的應用場景的題。
這也是本人第一次認真寫一個技術長文,雖然也沒有什么特別深奧的東西,讀到這里說明你也是很給我面子的了,之后還會繼續更新一些自己的想法和一些好的題目,希望大家多多支持!