- Find Peak Element II
There is an integer matrix which has the following features:
The numbers in adjacent positions are different.
The matrix has n rows and m columns.
For all i < m, A[0][i] < A[1][i] && A[n - 2][i] > A[n - 1][i].
For all j < n, A[j][0] < A[j][1] && A[j][m - 2] > A[j][m - 1].
We define a position P is a peek if:
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]
Find a peak element in this matrix. Return the index of the peak.
Example
Given a matrix:
[
[1 ,2 ,3 ,6 ,5],
[16,41,23,22,6],
[15,17,24,21,7],
[14,18,19,20,10],
[13,14,11,10,9]
]
return index of 41 (which is [1,1]) or index of 24 (which is [2,2])
Challenge
Solve it in O(n+m) time.
If you come up with an algorithm that you thought it is O(n log m) or O(m log n), can you prove it is actually O(n+m) or propose a similar but O(n+m) algorithm?
Notice
The matrix may contains multiple peeks, find any of them.
首先看一种错误的解法:
即先在rMid行中找到最大值,然后在对应列中找到最大值,然后又在其最大行中找最大值,…,如此不断找行/列中的最大值,直到行和列的二分查找的上下限间隔<=1为止。
class Solution {
public:
/*
* @param A: An integer matrix
* @return: The index of the peak
*/
vector<int> findPeakII(vector<vector<int>> &A) {
int nRow = A.size();
int nCol = A[0].size();
vector<int> res;
int rMin = 0, rMax = nRow - 1, rMid = rMax / 2;
int cMin = 0, cMax = nCol - 1, cMid = cMax / 2;
while(rMin + 1 < rMax && cMin + 1 < cMax) {
rMid = rMin + (rMax - rMin) / 2;
// find the peak in row rMid, rPeak is the index of a column
int rPeak = findPeak(A, cMin, cMax, rMid, 0);
if (A[rMid][rPeak] < A[rMid - 1][rPeak]) {
rMax = rMid;
} else if (A[rMid][rPeak] < A[rMid + 1][rPeak]) {
rMin = rMid;
} else {
return vector<int>{rMid, rPeak};
}
cMid = cMin + (cMax - cMin) / 2;
// find the peak in the col cMid, cPeak is the index of a row
int cPeak = findPeak(A, rMin, rMax, cMid, 1);
if (A[cPeak][cMid] < A[cPeak][cMid - 1]) {
cMax = cMid;
} else if (A[cPeak][cMid] < A[cPeak][cMid + 1]) {
cMin = cMid;
} else {
return vector<int>{cPeak, cMid};
}
}
int maxElem = INT_MIN, maxElemRow = -1, maxElemCol = -1;
vector<int> result;
if (maxElem <= A[rMin][cMin]) {
maxElem = A[rMin][cMin]; maxElemRow = rMin; maxElemCol = cMin;
}
if (maxElem <= A[rMin][cMax]) {
maxElem = A[rMin][cMax]; maxElemRow = rMin; maxElemCol = cMax;
}
if (maxElem <= A[rMax][cMin]) {
maxElem = A[rMax][cMin]; maxElemRow = rMax; maxElemCol = cMin;
}
if (maxElem <= A[rMax][cMax]) {
maxElem = A[rMax][cMax]; maxElemRow = rMax; maxElemCol = cMax;
}
return vector<int>{maxElemRow, maxElemCol};
}
private:
//flag = 0, find peak in row index, otherwise find peak in col index
int findPeak(vector<vector<int>>& A, int lowBound, int uppBound, int index, int flag) {
int start = lowBound;
int end = uppBound;
while(start + 1 < end) {
int mid = start + (end - start) / 2;
if (flag == 0) {
if (A[index][mid] > A[index][mid - 1] && A[index][mid] > A[index][mid + 1]) return mid;
if (A[index][mid] < A[index][mid - 1] && A[index][mid] > A[index][mid + 1]) end = mid;
else start = mid;
} else {
if (A[mid][index] > A[mid-1][index] && A[mid][index] > A[mid + 1][index]) return mid;
if (A[mid][index] < A[mid-1][index] && A[mid][index] > A[mid + 1][index]) end = mid;
else start = mid;
}
}
if (flag == 0)
return A[index][start] > A[index][end] ? start : end;
else
return A[start][index] > A[end][index] ? start : end;
}
};
但这种解法是不正确的。我个人认为如果矩阵全局只有一个波峰的话,它是正确的,但是如果矩阵存在多个波峰波谷,有时它会导致死循环。
我们看下面这个例子
[[1,2,1,2,1,2,1],
[2,17,13,6,5,17,2],
[1,15,8,10,8,15,1],
[2,14,12,11,12,14,2],
[1,2,1,2,1,2,1]]
上述解法的输出会在10->11->14->15中陷入死循环。
解法1:O(MlogN)
思路大致是每次沿着上升方向走,采用二分法。
注意
- 如果有多个波峰,该方法总能找到其中一个(不一定是最高峰)。
- while()循环结束后,如果还没找到,再比较一下rMin和rMax两行的最大值谁大,输出其中最大值即可。
- findPeakInRow()里面不能用binary search。因为我们要找row里面的最大值,而不是仅仅找到一个peak而已。
- peak也可以在边界或角上。可以想象成有一个都是0的虚拟外框套在这个矩形外面。
代码如下:
class Solution {
public:
/*
* @param A: An integer matrix
* @return: The index of the peak
*/
vector<int> findPeakII(vector<vector<int>> &A) {
int nRow = A.size();
int nCol = A[0].size();
vector<int> res;
int rMin = 0, rMax = nRow - 1;
while(rMin + 1 < rMax) {
int rMid = rMin + (rMax - rMin) / 2;
int index = findPeakInRow(A, rMid);
if (A[rMid][index] < A[rMid - 1][index]) {
rMax = rMid;
} else if (A[rMid][index] < A[rMid + 1][index]) {
rMin = rMid;
} else {
return vector<int>{rMid, index};
}
}
int rMaxIndex = findPeakInRow(A, rMax);
int rMinIndex = findPeakInRow(A, rMin);
if (A[rMin][rMinIndex] < A[rMax][rMaxIndex])
return vector<int>{rMax, rMaxIndex};
else
return vector<int>{rMin, rMinIndex};
}
private:
int findPeakInRow(vector<vector<int>>& A, int row) {
int col = 0;
int len = A[0].size();
for (int i = 1; i < len; ++i) {
if (A[row][col] < A[row][i]) col = i;
}
return col;
}
};
解法2:O(M+N)
参考了九章和这个链接。
http://courses.csail.mit.edu/6.006/spring11/lectures/lec02.pdf
思路:从矩形中间划一个十字架(中间列+中间行),该十字架会把矩形分成4个象限(不包括十字架自己),然后找出十字架行和列中所有元素的最大值(假设为x)。然后找该最大值的邻居有没有比它大的,若没有,则x就是一个peak,直接返回即可。若x有个邻居比它大,则看该邻居落在哪个象限,重新在该象限递归查找,直到返回一个peak。
为什么这个算法可行呢?假设x在中间列的上半部,而x的右邻居y比x大(即落在第一象限),那么根据题目定义,
For all j < n, A[j][0] < A[j][1] && A[j][m - 2] > A[j][m - 1].
则x和y对应的行在第一象限中必存在一个行的peak(假设为z, z>=y>=x)。
而z又大于十字架的中间行的所有元素(因为x是十字架上的最大元素)。
可知第一象限中至少有一个peak(行列都是peak)。此处证明似乎还不够严谨,下次补充。
该算法复杂度为O(M+N)。以正方形矩阵的情形证明如下:
T(n) = T(n/2) + cn
T(n) = T(n/4) + c(n/2) + cn
T(n) = T(n/8) + c(n/4) + c(n/2) + cn
…
T(n) = T(1) + c(1 + 2 + … + n/4 + n/2 + n) = O(n)
代码如下:
class Solution {
public:
/*
* @param A: An integer matrix
* @return: The index of the peak
*/
vector<int> findPeakII(vector<vector<int>> &A) {
int nRow = A.size();
int nCol = A[0].size();
return find(A, 1, nRow - 2, 1, nCol - 2);
}
private:
vector<int> find(vector<vector<int>>& A, int rStart, int rEnd, int cStart, int cEnd) {
int rMid = rStart + (rEnd - rStart) / 2;
int cMid = cStart + (cEnd - cStart) / 2;
int r = rMid, c = cMid;
int maxV = A[rMid][cMid];
for (int i = rStart; i <= rEnd; ++i) {
if (A[i][cMid] > maxV) {
maxV = A[i][cMid];
r = i;
c = cMid;
}
}
for (int i = cStart; i <= cEnd; ++i) {
if (A[rMid][i] > maxV) {
maxV = A[rMid][i];
r = rMid;
c = i;
}
}
if (A[r - 1][c] > A[r][c]) {
r -= 1;
} else if (A[r + 1][c] > A[r][c]) {
r += 1;
} else if (A[r][c - 1] > A[r][c]) {
c -= 1;
} else if (A[r][c + 1] > A[r][c]) {
c += 1;
} else {
// it is peak
return vector<int>{r, c};
}
if (r >= rStart && r < rMid && c >= cStart && c < cMid) {
return find(A, rStart, rMid - 1, cStart, cMid - 1);
}
if (r >= rStart && r < rMid && c > cMid && c <= cEnd) {
return find(A, rStart, rMid - 1, cMid + 1, cEnd);
}
if (r > rMid && r <= rEnd && c >= cStart && c < cMid) {
return find(A, rMid + 1, rEnd, cStart, cMid - 1);
}
if (r > rMid && r <= rEnd && c > cMid && c <= cEnd) {
return find(A, rMid + 1, rEnd, cMid + 1, cEnd);
}
return vector<int>();
}
};