配列のピークを特定

7191 ワード

このテーマは2つのタイプに分かれています.
1.一次元配列のピークを探す
整数配列(sizeはn)を与え、以下の特徴を有します.
  • の隣接する位置の数字は異なる
  • である.
  • A[0]A[n−1]
  • Pがピークの位置であると仮定するとA[P] > A[P-1],A[P] > A[P+1]を満たし,配列のいずれかのピークの位置を返す.
    1次元配列のピークを探して、最も暴力的な方法はfor循環で、1つの要素ごとに左右の両側の値を比較して、時間の複雑度O(N),.
    また,実際に最適なアルゴリズムは二分法,O(log N)である.
    class Solution {
    public:
        /**
         * @param A: An integers array.
         * @return: return any of peek positions.
         */
        // 1.           for   
        int findPeak1(vector 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 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 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列がある.
  • すべてのiA[n-1][i]がある.
  • すべてのjA[j][m-1]がある.

  • 位置Pがピークであると定義し,A[j][i]>A[j+1][i]&&A[j][i]>A[j-1][i]&&A[j][i]>A[j][i]>A[j][i+1]&&A[j][i]>A[j][i]>A[j][i]がある場合を定義する.
    この行列の1つのピーク要素を探し出して、彼の座標の解題の構想に戻って依然として2分で、行の2分で、同じく2分を列挙することができて、このような時間の複雑度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 findPeakII3(int[][] A) {
            // write your code here
            List ans = new ArrayList();
            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 findPeakII4(int[][] A) {
            List ans = new ArrayList();
            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 findPeakII(int[][] A) {
            List ans = new ArrayList();
            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)であるが,なぜかというと,以下に証明する.
    2分プロセスでは、行に対して中間行の最大値を2分スキャンし、行ごとにN個の数をスキャンし、列に対して2分して中間列の最大値を探します.この場合、前に2分したため、各列は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*2 N+O(1)=O(N)を相殺した.