LintCode 390: Find Peak Element II (Binary Search 经典题!!!)

  1. 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)
思路大致是每次沿着上升方向走,采用二分法。
注意

  1. 如果有多个波峰,该方法总能找到其中一个(不一定是最高峰)。
  2. while()循环结束后,如果还没找到,再比较一下rMin和rMax两行的最大值谁大,输出其中最大值即可。
  3. findPeakInRow()里面不能用binary search。因为我们要找row里面的最大值,而不是仅仅找到一个peak而已。
  4. 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) + O(n)
T(n) = T(n/4) + O(n/2) + O(n)
T(n) = T(n/8) + O(n/4) + O(n/2) + O(n)

T(n) = T(1) + O(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>();
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值