这种题目分为两种类型,
1. 找一维数组的峰值
你给出一个整数数组(size为n),其具有以下特点:
- 相邻位置的数字是不同的
- A[0] < A[1] 并且 A[n - 2] > A[n - 1]
假定P是峰值的位置则满足A[P] > A[P-1]
且A[P] > A[P+1]
,返回数组中任意一个峰值的位置。
找一维数组的峰值,最暴力的办法是for循环,逐个元素比较左右两边的值,时间复杂度O(N),.
另外其实最优的算法是二分法,O(log N)
class Solution {
public:
/**
* @param A: An integers array.
* @return: return any of peek positions.
*/
// 1. 第一类面试者 只会 for 循环
int findPeak1(vector<int> A) {
// write your code here
for(int i = 1; i < A.size()-1; i++){
if(A[i] > A[i-1] && A[i] > A[i+1]){
return i;
}
}
return -1;
}
// 2. 第二类面试者 知道二分法
int findPeak2(vector<int> A) {
if(A.size() < 3){
return -1;
}
int start = 1;
int end = A.size()-1;
while(start <= end){
int middle = start + (end - start) / 2;
if(A[middle] < A[middle-1]){
end = middle-1;
}
else if(A[middle] < A[middle+1]){
start = middle+1;
}
else {
return middle;
}
}
return -1;//should never happen
}
// 3. 第二类面试者 知道二分法(模板解法)
int findPeak(vector<int> A) {
if(A.size() < 3){
return -1;
}
int start = 1;
int end = A.size()-2;
while(start+1 < end){
int middle = start + (end - start) / 2;
if(A[middle] < A[middle-1]){
end = middle;
}
else if(A[middle] < A[middle+1]){
start = middle;
}
else {
start = middle;
}
}
if(A[start] > A[end]){
return start;
}
return end;
}
};
2. 找二维数组的峰值
一个整数矩阵有如下一些特性:
- 相邻的整数都是不同的
- 矩阵有 n 行 m 列。
- 对于所有的 i < m, 都有 A[0][i] < A[1][i] && A[n - 2][i] > A[n - 1][i].
- 对于所有的 j < n, 都有 A[j][0] < A[j][1] && A[j][m - 2] > A[j][m - 1].
我们定义一个位置 P 是一个峰,如果有 A[j][i] > A[j+1][i] && A[j][i] > A[j-1][i] && A[j][i] > A[j][i+1] && A[j][i] > A[j][i-1]。
找出该矩阵的一个峰值元素,返回他的坐标
解题思路仍然是二分,可以行二分,也可以列二分,这样时间复杂度O(N log N), 参见下面的解法5最优的方法是行列同时二分 O(N)
class Solution {
/**
* @param A: An integer matrix
* @return: The index of the peak
*/
// 解法4, 第三类面试者 只会O(N * M)的解法
public List<Integer> findPeakII3(int[][] A) {
// write your code here
List<Integer> ans = new ArrayList<Integer>();
int n = A.length;
int m = A[0].length;
for(int i = 1; i < n-1; i++){
for(int j = 1; j < m-1; j++){
int[] dx = {-1, 1, 0, 0};
int[] dy = {0, 0, -1, 1};
if( A[i][j] > A[i+dx[0]][j+dy[0]] &&
A[i][j] > A[i+dx[1]][j+dy[1]] &&
A[i][j] > A[i+dx[2]][j+dy[2]] &&
A[i][j] > A[i+dx[3]][j+dy[3]]){
ans.add(i);
ans.add(j);
return ans;
}
}
}
return ans;
}
// 解法5, 第四类面试者 会优化,会O(N log N)的解法
public List<Integer> findPeakII4(int[][] A) {
List<Integer> ans = new ArrayList<Integer>();
int n = A.length;
int m = A[0].length;
if(n < 3){
return ans; // argument check
}
int start = 1, end = n-2;
while(start <= end){
int row = start + (end - start) / 2;
int col = findLargest(A, row);
if(A[row][col] < A[row-1][col]){
end = row - 1;
}
else if(A[row][col] < A[row+1][col]){
start = row + 1;
}
else{
ans.add(row);
ans.add(col);
return ans;
}
}
return ans;
}
// find largest in a row, return col index
public int findLargest(int [][] A, int row){
int col = 0;
for(int j = 0; j < A[0].length; j++){
if(A[row][j] > A[row][col]){
col = j;
}
}
return col;
}
// 解法六, 第五类面试者 会举一反四,会O(N)的解法
public List<Integer> findPeakII(int[][] A) {
List<Integer> ans = new ArrayList<Integer>();
int n = A.length;
int m = A[0].length;
if(n < 3 || m < 3){
return ans; // argument check
}
int start_row = 1, end_row = n-2;
int start_col = 1, end_col = m-2;
int row = 0, col = 0; // store middle
while(start_row <= end_row && start_col <= end_col){
// search row
row = start_row + (end_row - start_row) / 2;
col = findLargestInRow(A, row, start_col, end_col);
if(A[row][col] < A[row-1][col]){
end_row = row - 1;
}
else if(A[row][col] < A[row+1][col]){
start_row = row + 1;
}
else{
ans.add(row);
ans.add(col);
return ans;
}
// search column
col = start_col + (end_col - start_col) / 2;
row = findLargestInCol(A, col, start_row, end_row);
if(A[row][col] < A[row][col-1]){
end_col = col - 1;
}
else if(A[row][col] < A[row][col+1]){
start_col = col + 1;
}
else{
ans.add(row);
ans.add(col);
return ans;
}
}
return ans;
}
// search larget one in a row, return col index
public int findLargestInRow(int [][] A, int row, int start_col, int end_col){
int col = start_col;
for(int j = start_col; j <= end_col; j++){
if(A[row][j] > A[row][col]){
col = j;
}
}
return col;
}
// search largest one in a col, return row index;
public int findLargestInCol(int [][] A, int col, int start_row, int end_row){
int row = start_row;
for(int i = start_row; i <= end_row; i++){
if(A[i][col] > A[row][col]){
row = i;
}
}
return row;
}
}
最后一种解法是最优的解法,行和列同时二分,同样是利用了一维数组找峰值的思想。时间复杂度是O(N), 为什么呢,下面给出证明:
二分过程中要对行二分扫描中间这一行的最大值,每一行N个数, 然后对列二分找中间一列的最大值,这时候由于之前行二分过,所以每一列只有n/2(或相同数量级)个数,然后再找出(N/2)小矩阵的峰值,这是相同的子问题。于是
T(N) = N + N/2 + T(N/2) = 3/2 * N + O(N)
T(N/2) = 3/2 * (N/2) + O(N/4)
T(N/4) = 3/2 * (N/4) + O(N/8)
T(N/k) = 3/2 * (N/k) + O(1)
以此类推,所有等式相加起来,抵消得到 T(N) = 3/2 * 2N + O(1) = O(N) 。