关于c ++:在已排序和旋转的数组中搜索

Searching in an sorted and rotated array

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

已经为您提供了一个排序后旋转的数组。

例子

让排序后旋转的arr = [1,2,3,4,5]向右说两次

[4,5,1,2,3]

现在,如何才能最好地搜索这个排序+旋转数组?

可以先取消数组的汇总,然后进行二进制搜索。但这并不比在输入数组中进行线性搜索更好,因为两者都是最坏的情况O(N)。

请提供一些指针。我在谷歌上搜索了很多关于这个的特殊算法,但找不到任何。

我理解C和C++


这可以在O(logN)中使用稍微修改过的二进制搜索来完成。

排序+旋转数组的有趣特性是,当您将其分成两半时,两半中的至少一个始终会被排序。

1
2
3
4
5
6
7
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

似乎左子数组排序时右子数组未排序。

如果中间恰好是旋转点,则左、右子数组都将被排序。

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

但在任何情况下,都必须对一半(子数组)进行排序。

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

一旦我们找到哪一半是排序的,我们就可以看到键是否存在于与极值的半简单比较中。

如果键在那一半中,我们递归地调用那一半上的函数否则,我们递归地调用另一半的搜索。

我们将在每个调用中丢弃数组的一半,这使得该算法成为O(logN)

伪代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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

这里的关键是始终要对一个子数组进行排序,使用它我们可以丢弃该数组的一半。


您可以进行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)

编辑:代码。


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

这一采访问题在《破解编码采访》一书中有详细讨论。在那本书中特别讨论了重复元素的条件。因为OP在评论中说数组元素可以是任何东西,所以我在下面给出了伪代码的解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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


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


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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;
}


如果您知道数组已经向右旋转了s,您可以简单地执行二进制搜索,将s右移。这是O(LG n)

通过这个,我的意思是,初始化左极限到s,右极限到(s-1)mod n,然后在它们之间进行二进制搜索,注意在正确的区域工作。

如果不知道数组旋转了多少,可以使用二进制搜索确定旋转的大小,即o(lg n),然后执行移位二进制搜索,o(lg n),总共还有o(lg n)。


如果你知道它旋转了多远,你仍然可以进行二进制搜索。

诀窍是你得到两个级别的索引:你在虚拟的0..n-1范围内做b.s.,然后在实际查找值时取消旋转它们。


您不需要首先旋转数组,可以在旋转的数组上使用二进制搜索(进行一些修改)。

假设n是您搜索的数字:

读取第一个数字(arr[开始])和数组中间的数字(arr[结束]):

  • 如果arr[start]>arr[end]->前半部分未排序,但后半部分排序:

    • 如果arr[end]>n->数字在索引中:(middle+n-arr[end])

    • 如果n在数组的第一部分重复搜索(请参见end to be the middle of the first half of the array etc.)。

(如果第一部分已排序,第二部分未排序,则相同)


回复上述帖子"本访谈问题在《破解编码访谈》一书中详细讨论"。在那本书中特别讨论了重复元素的条件。由于op在注释中说数组元素可以是任何东西,所以我在下面给出了伪代码的解决方案:"

你的解决方案是O(N)!!(在最后一个if条件中,您检查数组的两部分是否存在单个条件,这使其成为线性时间复杂性的sol)

比起在一轮编码过程中陷入由错误和分段错误组成的迷宫,我更擅长进行线性搜索。

我认为没有比O(N)更好的解决方案来搜索旋转排序数组(有重复项)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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;
}


首先,你需要找到移位常数k。这可以在O(lgn)时间内完成。从常量shift k中,可以很容易地找到要使用的元素一个常数k的二进制搜索。增广的二进制搜索也需要O(lgn)时间。总运行时间为o(lgn+lgn)=o(lgn)

要找到常量移位,只需在数组中查找最小值。数组最小值的索引告诉您常量移位。考虑排序数组[1,2,3,4,5]。

1
2
3
4
5
6
7
The possible shifts are:
    [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++中的算法代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// 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
2
3
Hope this helps!=)
Soon Chee Loong,
University of Toronto

问题:在旋转排序数组中搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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;
    }
}

这里有一个简单的(时间、空间)高效的非递归O(log n)python解决方案,它不修改原始数组。将旋转的数组切成两半,直到我只有两个要检查的索引,如果一个索引匹配,则返回正确的答案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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

另一种处理重复值的方法是找到旋转,然后在每次访问数组时应用旋转进行常规的二进制搜索。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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))

试试这个解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
}


对于具有重复的旋转数组,如果需要找到元素的第一个出现,可以使用下面的过程(Java代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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条件如下:

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

我的简单代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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(对数(n))。


C++中的代码应该适用于所有的情况,虽然它与复制品一起工作,如果这个代码中有bug,请告诉我。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#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<<"
"
;
    return 0;
}