在已排序和旋转的数组中搜索

Jon*_*nes 68 c c++ arrays algorithm

在准备技术面试时,我偶然发现了这个有趣的问题:

您已经获得了一个已排序然后旋转的数组.

arr = [1,2,3,4,5]哪个被排序然后旋转说两次给右边

[4,5,1,2,3]

现在,如何在这个已排序+旋转的数组中进行最佳搜索?

可以取消旋转数组,然后进行二分查找.但这并不比在输入数组中进行线性搜索更好,因为两者都是最坏的情况O(N).

请提供一些指示.我已经搜索了很多特殊算法,但找不到任何算法.

我理解c和c ++

cod*_*ict 161

这可以O(logN)使用略微修改的二进制搜索来完成.

排序+旋转数组的有趣属性是,当您将其分成两半时,将始终对两半中的一半进行排序.

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
Run Code Online (Sandbox Code Playgroud)

因为看起来正确的子数组没有排序,而左子数组是排序的.

如果mid恰好是旋转点,则左右子阵列将被排序.

[6,7,8,9,1,2,3,4,5]
         ^
Run Code Online (Sandbox Code Playgroud)

无论如何必须对一半(子阵列)进行排序.

通过比较每一半的开始和结束元素,我们可以很容易地知道哪一半是排序的.

一旦我们找到哪一半被分类,我们就可以看到该键是否存在于那一半 - 与极端的简单比较.

如果密钥出现在那一半中,我们递归调用该函数的那一半
,我们递归调用另一半的搜索.

我们在每个调用中丢弃一半的数组,这使得这个算法成为可能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
Run Code Online (Sandbox Code Playgroud)

这里的关键是总是对一个子数组进行排序,使用它可以丢弃数组的一半.

  • 很好的解释 :) (6认同)
  • 简单.简洁.例子.FTW !! (3认同)
  • 不适用于重复元素.但好的解决方案. (3认同)
  • Thnanks codeaddict清楚解释. (2认同)
  • 解决方案中应包含重复项的更改应该是什么,因为这不会使包含重复项的数组像在{{10,15,10,10,10,10,10,10,10,10,10 ,10}`? (2认同)
  • “排序+旋转数组的有趣特性是,将其分为两半时,将始终对这两个半数中的至少一个进行排序。” 这使天才或有经验的人与新手分开。谢谢,这一行帮助我可视化了整个解决方案。 (2认同)

Max*_*Max 17

你可以做2个二进制搜索:首先要找到索引i,使得arr[i] > arr[i+1].

显然,(arr\[1], arr[2], ..., arr[i])并且 (arr[i+1], arr[i+2], ..., arr[n])都是排序的数组.

然后,如果arr[1] <= x <= arr[i],你在第一个数组进行二进制搜索,否则在第二个数组进行.

复杂性 O(logN)

编辑: 代码.

  • 为什么你必须明确地搜索"突破点"?为什么不直接使用修改后的二进制搜索来搜索元素并同时检查"异常"? (4认同)

Chu*_*cks 14

当数组中存在重复元素时,所选答案有错误.例如,arr = {2,3,2,2,2}我们正在寻找3.然后,所选答案中的程序将返回-1而不是1.

这个面试问题在"破解编码面试"一书中有详细讨论.该书中特别讨论了重复元素的条件.由于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
Run Code Online (Sandbox Code Playgroud)

  • 我认为你是唯一一个正确考虑重复元素的人.但是你的方法并不能保证对数的复杂性.特别是在输入上,如5,5,5,5,5,5,......(很多修正),5,1,5 (4认同)

Rom*_*anK 8

我的第一次尝试是使用二进制搜索找到应用的旋转次数 - 这可以通过使用通常的二进制搜索机制找到a [n]> a [n + 1]的索引n来完成.然后进行常规二进制搜索,同时旋转每个班次的所有索引.

  • @Jones:这将是一个修改过的二进制搜索.您正在寻找两个相邻值减小的点.猜猜一个指数.如果该索引处的值大于数组中的第一个值,那么请继续查看猜测的右侧.如果它更少,继续向左看.但是,如果你实际上不关心*不连续性,你只想进行搜索,那么codaddict的答案会更好. (2认同)

Akk*_*ved 5

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;
}
Run Code Online (Sandbox Code Playgroud)