web-dev-qa-db-ja.com

ソートおよび回転された配列での検索

インタビューの準備中に、私はこの興味深い質問に出くわしました。

ソートされてから回転される配列が与えられました。

例えば:

  • arr = [1,2,3,4,5]、ソートされます
  • 右に2回回転して[4,5,1,2,3]

さて、この並べ替えられた+回転された配列でどれだけ最適に検索できますか?

配列の回転を解除してから、バイナリ検索を実行できます。ただし、どちらもワーストケースのO(N)であるため、これは入力配列で線形検索を行うよりも優れています。

いくつかのポインタを提供してください。私はこのための特別なアルゴリズムについて多くのことをグーグルで調べましたが、見つかりませんでした。

CとC++を理解しています。

65
Jones

これは、わずかに変更されたバイナリ検索を使用して、O(logN)で実行できます。

ソートされた+回転した配列の興味深い特性は、それを2つの半分に分割すると、少なくとも2つの半分のいずれかが常にソートされることです。

_Let input array arr = [4,5,6,7,8,9,1,2,3]
number of elements  = 9
mid index = (0+8)/2 = 4

[4,5,6,7,8,9,1,2,3]
         ^
 left   mid  right
_

右のサブアレイはソートされていないように見えますが、左のサブアレイはソートされています。

Midが回転のポイントである場合、それらは左右のサブ配列の両方がソートされます。

_[6,7,8,9,1,2,3,4,5]
         ^
_

ただし、いずれの場合でも、半分(サブ配列)をソートする必要があります

各半分の開始要素と終了要素を比較することで、どの半分がソートされているかを簡単に知ることができます。

どちらの半分がソートされているかを確認すると、その半分にキーが存在するかどうかを確認できます-極端との単純な比較。

キーがその半分に存在する場合、その半分で関数を再帰的に呼び出します
それ以外の場合は、残りの半分を再帰的に呼び出します。

このアルゴリズムをO(logN)にする各呼び出しで配列の半分を破棄しています。

擬似コード:

_function search( arr[], key, low, high)

        mid = (low + high) / 2

        // key not present
        if(low > high)
                return -1

        // key found
        if(arr[mid] == key)
                return mid

        // if left half is sorted.
        if(arr[low] <= arr[mid])

                // if key is present in left half.
                if (arr[low] <= key && arr[mid] >= key) 
                        return search(arr,key,low,mid-1)

                // if key is not present in left half..search right half.
                else                 
                        return search(arr,key,mid+1,high)
                end-if

        // if right half is sorted. 
        else    
                // if key is present in right half.
                if(arr[mid] <= key && arr[high] >= key) 
                        return search(arr,key,mid+1,high)

                // if key is not present in right half..search in left half.
                else
                        return search(arr,key,low,mid-1)
                end-if
        end-if  

end-function
_

ここで重要なのは、1つのサブ配列が常にソートされ、それを使用して配列の半分を破棄できることです。

155
codaddict

2つのバイナリ検索を実行できます。まず、_arr[i] > arr[i+1]_のようなインデックスiを見つけます。

明らかに、_(arr\[1], arr[2], ..., arr[i])_と_(arr[i+1], arr[i+2], ..., arr[n])_は両方ともソートされた配列です。

次に、_arr[1] <= x <= arr[i]_の場合、最初の配列でバイナリ検索を実行し、そうでない場合は2番目の配列で実行します。

複雑さO(logN)

編集: コード

15
Max

配列に重複した要素がある場合、受け入れられた回答にはバグがあります。例えば、 arr = {2,3,2,2,2}と3が探しています。次に、受け入れられた回答のプログラムは1ではなく-1を返します。

このインタビューの質問は、「Cracking the Coding Interview」で詳しく説明されています。重複要素の条件は、その本で特に説明されています。 opはコメントで配列要素は何でもかまわないと言ったので、以下に擬似コードとして解決策を示します。

function search( arr[], key, low, high)

    if(low > high)
        return -1

    mid = (low + high) / 2

    if(arr[mid] == key)
        return mid

    // if the left half is sorted.
    if(arr[low] < arr[mid]) {

        // if key is in the left half
        if (arr[low] <= key && key <= arr[mid]) 
            // search the left half
            return search(arr,key,low,mid-1)
        else
            // search the right half                 
            return search(arr,key,mid+1,high)
        end-if

    // if the right half is sorted. 
    else if(arr[mid] < arr[low])    
        // if the key is in the right half.
        if(arr[mid] <= key && arr[high] >= key) 
            return search(arr,key,mid+1,high)
        else
            return search(arr,key,low,mid-1)
        end-if

    else if(arr[mid] == arr[low])

        if(arr[mid] != arr[high])
            // Then elements in left half must be identical. 
            // Because if not, then it's impossible to have either arr[mid] < arr[high] or arr[mid] > arr[high]
            // Then we only need to search the right half.
            return search(arr, mid+1, high, key)
        else 
            // arr[low] = arr[mid] = arr[high], we have to search both halves.
            result = search(arr, low, mid-1, key)
            if(result == -1)
                return search(arr, mid+1, high, key)
            else
                return result
   end-if
end-function
14
ChuanRocks

私の最初の試みは、バイナリ検索を使用して適用された回転数を見つけることです-これは、通常のバイナリ検索メカニズムを使用して、a [n]> a [n + 1]のインデックスnを見つけることで実行できます。次に、見つかったシフトごとにすべてのインデックスを回転させながら、通常のバイナリ検索を実行します。

8
RomanK
int rotated_binary_search(int A[], int N, int key) {
  int L = 0;
  int R = N - 1;

  while (L <= R) {
    // Avoid overflow, same as M=(L+R)/2
    int M = L + ((R - L) / 2);
    if (A[M] == key) return M;

    // the bottom half is sorted
    if (A[L] <= A[M]) {
      if (A[L] <= key && key < A[M])
        R = M - 1;
      else
        L = M + 1;
    }
    // the upper half is sorted
    else {
      if (A[M] < key && key <= A[R])
        L = M + 1;
      else
        R = M - 1;
    }
  }
  return -1;
}
5
Akki Javed

配列がsを右に回転したことがわかっている場合は、sを右にシフトしたバイナリ検索を実行できます。これはO(lg N)です

つまり、左の制限をsに、右の制限を(s-1)mod Nに初期化し、これらの間でバイナリ検索を行い、正しい領域で作業するように少し注意します。

配列の回転量がわからない場合は、バイナリ検索(O(lg N))を使用して回転の大きさを判断し、シフトバイナリ検索O(lg N)、まだO(lg N)の総計。

上記の投稿への返信「このインタビューの質問は、「Cracking the Coding Interview」という本で詳しく説明されています。重複する要素の条件は、この本で特に説明されています。私のソリューションを以下の擬似コードとして提供しています:」

あなたのソリューションはO(n) !!(配列の両方の半分を単一の条件でチェックする最後のif条件により、線形時間の複雑さのソルになります)

コーディングラウンド中にバグやセグメンテーションフォールトの迷路にとどまるよりも、線形検索を行う方が良いです。

回転したソート済み配列での検索(重複あり)の場合、O(n)よりも良い解決策があるとは思わない

2
NIKUNJ BHARTIA

どのように(どれだけ)回転したかがわかっている場合でも、バイナリ検索を実行できます。

秘Theは、2つのレベルのインデックスを取得することです。b.sを実行します。仮想0..n-1の範囲内で、実際に値を検索するときにそれらを回転解除します。

2
Henk Holterman

最初に配列を回転する必要はありません。回転した配列でバイナリ検索を使用できます(いくつかの変更を加えます)

nが検索する番号であると仮定します。

最初の番号(arr [start])と配列の中央の番号(arr [end])を読み取ります。

  • arr [start]> arr [end]->前半はソートされないが、後半はソートされる場合:

    • arr [end]> N->数値がインデックスにある場合:(中間+ N-arr [end])

    • nが配列の最初の部分で検索を繰り返す場合(endが配列の最初の半分の真ん中になるなどを参照)

(最初の部分がソートされているが、2番目の部分がソートされていない場合も同じです)

2
SivGo
short mod_binary_search( int m, int *arr, short start, short end)
{

 if(start <= end)
 {
    short mid = (start+end)/2;

    if( m == arr[mid])
        return mid;
    else
    {
        //First half is sorted
        if(arr[start] <= arr[mid])
        {
            if(m < arr[mid] && m >= arr[start])
                return mod_binary_search( m, arr, start, mid-1);
            return mod_binary_search( m, arr, mid+1, end);
        }

        //Second half is sorted
        else
        {
            if(m > arr[mid] && m < arr[start])
                return mod_binary_search( m, arr, mid+1, end);
            return mod_binary_search( m, arr, start, mid-1);
        }
    }
 }
 return -1;
}
1
public class PivotedArray {

//56784321 first increasing than decreasing
public static void main(String[] args) {
    // TODO Auto-generated method stub
    int [] data ={5,6,7,8,4,3,2,1,0,-1,-2};

    System.out.println(findNumber(data, 0, data.length-1,-2));

}

static int findNumber(int data[], int start, int end,int numberToFind){

    if(data[start] == numberToFind){
        return start;
    }

    if(data[end] == numberToFind){
        return end;
    }
    int mid = (start+end)/2;
    if(data[mid] == numberToFind){
        return mid;
    }
    int idx = -1;
    int midData = data[mid];
    if(numberToFind < midData){
        if(midData > data[mid+1]){
            idx=findNumber(data, mid+1, end, numberToFind);
        }else{
            idx =  findNumber(data, start, mid-1, numberToFind);
        }
    }

    if(numberToFind > midData){
        if(midData > data[mid+1]){
            idx =  findNumber(data, start, mid-1, numberToFind);

        }else{
            idx=findNumber(data, mid+1, end, numberToFind);
        }
    }
    return idx;
}

}
1
RockSolid

最初に、シフト定数kを見つける必要があります。これはO(lgN) time。で実行できます。定数シフトkから、定数kを使用したバイナリ検索を使用して、探している要素を簡単に見つけることができます。拡張バイナリ検索また、O(lgN) timeを取ります。合計実行時間はO(lgN + lgN)= O(lgN)です。

定数シフトを見つけるには、k。配列内の最小値を探すだけです。配列の最小値のインデックスは、一定のシフトを示します。ソートされた配列[1,2,3,4,5]を検討してください。

可能なシフトは次のとおりです。
 [1,2,3,4,5] // k = 0 
 [5,1,2,3,4] // k = 1 
 [4,5,1,2,3] // k = 2 
 [3,4,5,1,2] // k = 3 
 [ 2,3,4,5,1] // k = 4 
 [1,2,3,4,5] // k = 5%5 = 0 

O(lgN)時間でアルゴリズムを実行するには、常に問題を半分に分割する方法を見つけることが重要です。一度実行すると、実装の詳細の残りの部分は簡単になります。

以下は、アルゴリズムのC++のコードです。

// This implementation takes O(logN) time
// This function returns the amount of shift of the sorted array, which is
// equivalent to the index of the minimum element of the shifted sorted array. 
#include <vector> 
#include <iostream> 
using namespace std; 

int binarySearchFindK(vector<int>& nums, int begin, int end)
{
    int mid = ((end + begin)/2); 
    // Base cases
    if((mid > begin && nums[mid] < nums[mid-1]) || (mid == begin && nums[mid] <= nums[end]))     
        return mid; 
    // General case 
    if (nums[mid] > nums[end]) 
    {
        begin = mid+1; 
        return binarySearchFindK(nums, begin, end); 
    }
    else
    {
        end = mid -1; 
        return binarySearchFindK(nums, begin, end); 
    }   
}  
int getPivot(vector<int>& nums)
{
    if( nums.size() == 0) return -1; 
    int result = binarySearchFindK(nums, 0, nums.size()-1); 
    return result; 
}

// Once you execute the above, you will know the shift k, 
// you can easily search for the element you need implementing the bottom 

int binarySearchSearch(vector<int>& nums, int begin, int end, int target, int pivot)
{
    if (begin > end) return -1; 
    int mid = (begin+end)/2;
    int n = nums.size();  
    if (n <= 0) return -1; 

    while(begin <= end)
    {
        mid = (begin+end)/2; 
        int midFix = (mid+pivot) % n; 
        if(nums[midFix] == target) 
        {
            return midFix; 
        }
        else if (nums[midFix] < target)
        {
            begin = mid+1; 
        }
        else
        {
            end = mid - 1; 
        }
    }
    return -1; 
}
int search(vector<int>& nums, int target) {
    int pivot = getPivot(nums); 
    int begin = 0; 
    int end = nums.size() - 1; 
    int result = binarySearchSearch(nums, begin, end, target, pivot); 
    return result; 
}
これがお役に立てば幸いです!=)
まもなくチー・ローン、
トロント大学
1
Chee Loong Soon

C++のこのコードはすべてのケースで機能するはずです。重複して機能しますが、このコードにバグがある場合はお知らせください。

#include "bits/stdc++.h"
using namespace std;
int searchOnRotated(vector<int> &arr, int low, int high, int k) {

    if(low > high)
        return -1;

    if(arr[low] <= arr[high]) {

        int p = lower_bound(arr.begin()+low, arr.begin()+high, k) - arr.begin();
        if(p == (low-high)+1)
            return -1;
        else
            return p; 
    }

    int mid = (low+high)/2;

    if(arr[low] <= arr[mid]) {

        if(k <= arr[mid] && k >= arr[low])
            return searchOnRotated(arr, low, mid, k);
        else
            return searchOnRotated(arr, mid+1, high, k);
    }
    else {

        if(k <= arr[high] && k >= arr[mid+1])
            return searchOnRotated(arr, mid+1, high, k);
        else
            return searchOnRotated(arr, low, mid, k);
    }
}
int main() {

    int n, k; cin >> n >> k;
    vector<int> arr(n);
    for(int i=0; i<n; i++) cin >> arr[i];
    int p = searchOnRotated(arr, 0, n-1, k);
    cout<<p<<"\n";
    return 0;
}
0
Siddhant

私の簡単なコード:-

public int search(int[] nums, int target) {
    int l = 0;
    int r = nums.length-1;
    while(l<=r){
        int mid = (l+r)>>1;
        if(nums[mid]==target){
            return mid;
        }
        if(nums[mid]> nums[r]){
            if(target > nums[mid] || nums[r]>= target)l = mid+1;
            else r = mid-1;
        }
        else{
            if(target <= nums[r] && target > nums[mid]) l = mid+1;
            else r = mid -1;
        }
    }
    return -1;
}

時間の複雑さO(log(N))。

0
HeadAndTail

重複した回転配列の場合、要素の最初の出現を見つける必要がある場合、以下の手順を使用できます(Javaコード):

public int mBinarySearch(int[] array, int low, int high, int key)
{
    if (low > high)
        return -1; //key not present

    int mid = (low + high)/2;

    if (array[mid] == key)
        if (mid > 0 && array[mid-1] != key)
            return mid;

    if (array[low] <= array[mid]) //left half is sorted
    {
        if (array[low] <= key && array[mid] >= key)
            return mBinarySearch(array, low, mid-1, key);
        else //search right half
            return mBinarySearch(array, mid+1, high, key);
    }
    else //right half is sorted
    {
        if (array[mid] <= key && array[high] >= key)
            return mBinarySearch(array, mid+1, high, key);
        else
            return mBinarySearch(array, low, mid-1, key);
    }       

}

これは、上記のcodaddictの手順の改善です。以下の追加のif条件に注意してください。

if (mid > 0 && array[mid-1] != key)
0
ranjeeth1978

質問:回転したソート済み配列での検索

public class SearchingInARotatedSortedARRAY {
    public static void main(String[] args) {
        int[] a = { 4, 5, 6, 0, 1, 2, 3 };

        System.out.println(search1(a, 6));

    }

    private static int search1(int[] a, int target) {
        int start = 0;
        int last = a.length - 1;
        while (start + 1 < last) {
            int mid = start + (last - start) / 2;

            if (a[mid] == target)
                return mid;
            // if(a[start] < a[mid]) => Then this part of the array is not rotated
            if (a[start] < a[mid]) {
                if (a[start] <= target && target <= a[mid]) {
                    last = mid;
                } else {
                    start = mid;
                }
            }
            // this part of the array is rotated
            else {
                if (a[mid] <= target && target <= a[last]) {
                    start = mid;
                } else {
                    last = mid;
                }
            }
        } // while
        if (a[start] == target) {
            return start;
        }
        if (a[last] == target) {
            return last;
        }
        return -1;
    }
}
0
Soudipta Dutta

これは、元の配列を変更しない単純な(時間、空間)効率の高い非再帰的O(log n)pythonソリューションです。インデックスをチェックして、1つのインデックスが一致した場合に正しい答えを返します。

def findInRotatedArray(array, num):

lo,hi = 0, len(array)-1
ix = None


while True:


    if hi - lo <= 1:#Im down to two indices to check by now
        if (array[hi] == num):  ix = hi
        Elif (array[lo] == num): ix = lo
        else: ix = None
        break

    mid = lo + (hi - lo)/2
    print lo, mid, hi

    #If top half is sorted and number is in between
    if array[hi] >= array[mid] and num >= array[mid] and num <= array[hi]:
        lo = mid

    #If bottom half is sorted and number is in between
    Elif array[mid] >= array[lo] and num >= array[lo] and num <= array[mid]:
        hi = mid


    #If top half is rotated I know I need to keep cutting the array down
    Elif array[hi] <= array[mid]:
        lo = mid

    #If bottom half is rotated I know I need to keep cutting down
    Elif array[mid] <= array[lo]:
        hi = mid

print "Index", ix
0
Leon

繰り返される値で機能する別のアプローチは、回転を見つけて、配列にアクセスするたびに回転を適用する通常のバイナリ検索を実行することです。

test = [3, 4, 5, 1, 2]
test1 = [2, 3, 2, 2, 2]

def find_rotated(col, num):
    pivot = find_pivot(col)
    return bin_search(col, 0, len(col), pivot, num)

def find_pivot(col):
    prev = col[-1]
    for n, curr in enumerate(col):
        if prev > curr:
            return n
        prev = curr
    raise Exception("Col does not seem like rotated array")

def rotate_index(col, pivot, position):
    return (pivot + position) % len(col)

def bin_search(col, low, high, pivot, num):
    if low > high:
        return None
    mid = (low + high) / 2
    rotated_mid = rotate_index(col, pivot, mid)
    val = col[rotated_mid]
    if (val == num):
        return rotated_mid
    Elif (num > val):
        return bin_search(col, mid + 1, high, pivot, num)
    else:
        return bin_search(col, low, mid - 1,  pivot, num)

print(find_rotated(test, 2))
print(find_rotated(test, 4))
print(find_rotated(test1, 3))
0
barracel

この解決策を試してください

bool search(int *a, int length, int key)
{
int pivot( length / 2 ), lewy(0), prawy(length);
if (key > a[length - 1] || key < a[0]) return false;
while (lewy <= prawy){
    if (key == a[pivot]) return true;
    if (key > a[pivot]){
        lewy = pivot;
        pivot += (prawy - lewy) / 2 ? (prawy - lewy) / 2:1;}
    else{
        prawy = pivot;
        pivot -= (prawy - lewy) / 2 ? (prawy - lewy) / 2:1;}}
return false;
}
0
Bart