views:

448

answers:

6

We want to search for a given element in a circular sorted array in complexity not greater than O(Log n). ex: search for 13 in {5,9,13,1,3}. My idea was to convert the circular array into a regular sorted array then do a binary search on the resulting array, but my problem was the algorithm i came up was stupid that it takes O(n) in the worst case:

for(i = 1; i < a.length; i++){
    if (a[i] < a[i-1]){
        minIndex = i; break;
    }
}

then the corresponding index of ith element will be determined from the following relation:

(i + minInex - 1) % a.length

it is clear that my conversion (from circular to regular) algorithm may take O(n), so we need a better one.

According to ire_and_curses idea, here is the solution in java:

public int circularArraySearch(int[] a, int low, int high, int x){
    //instead of using the division op. (which surprisingly fails on big numbers)
    //we will use the unsigned right shift to get the average
    int mid = (low + high) >>> 1;
    if(a[mid] == x){
        return mid;
    }
    //a variable to indicate which half is sorted
    //1 for left, 2 for right
    int sortedHalf = 0;
    if(a[low] <= a[mid]){
        //the left half is sorted
        sortedHalf = 1;
        if(x <= a[mid] && x >= a[low]){
            //the element is in this half
            return binarySearch(a, low, mid, x);
        }
    }
    if(a[mid] <= a[high]){
        //the right half is sorted
        sortedHalf = 2;
        if(x >= a[mid] && x<= a[high] ){
            return binarySearch(a, mid, high, x);
        }
    }
    // repeat the process on the unsorted half
    if(sortedHalf == 1){
        //left is sorted, repeat the process on the right one
        return circularArraySearch(a, mid, high, x);
    }else{
        //right is sorted, repeat the process on the left
        return circularArraySearch(a, low, mid, x);
    }
}

Hopefully this will work, thanks Moron for letting me know.

A: 

You can use binary search to find the location of smallest element and reduce it to O(Log n).

You can find the location by (this is just a sketch of algorithm, it's inaccurate but you can get the idea from it):
1. i <- 1
2. j <- n
3. while i < j
3.1. k <- (j-i) / 2
3.2. if arr[k] < arr[i] then j <- k 3.3. else i <- k

After finding the location of the smallest element you can treat the array as two sorted arrays.

Elisha
that's a good start. thanks
guirgis
+6  A: 

Not very elegant, but of the top off my head - just use binary search to find the pivot of the rotated array, and then perform binary search again, compensating for the offset of the pivot. Kind of silly to perform two full searches, but it does fulfill the condition, since O(log n) + O(log n) == O(log n). Keep it simple and stupid(tm)!

Kilian Foth
+1: simple is good. it would probably be possible to do both at the same time; looking for wrapping point need to test if the order values for the current low and high indexes is reversed, if it is not then you are on the segment with no wrapping point and if(!) the sought value is in this segment then regular binary search can take care of it; if the number is not inside this segment or the segment has a wrapping point in it the split it and repeat; this way you either find the wrapping point or you reduce it to a regular binary search without finding it.
Unreason
@Unreason - you just described the algorithm in my answer. ;)
ire_and_curses
+2  A: 

You have three values, l,m,h for the values at the low, mid and high indexes of your search. If you think were you would continue searching for each possibility:

// normal binary search
l < t < m    - search(t,l,m)
m < t < h    - search(t,m,h)

// search over a boundary
l > m, t < m - search(t,l,m)
l > m, t > l - search(t,l,m)
m > h, t > m - search(t,m,h)  
m > h, t < h - search(t,m,h)  

It's a question of considering where the target value could be, and searching that half of the space. At most one half of the space will have the wrap-over in it, and it is easy to determine whether or not the target value is in that half or the other.

It's sort of a meta question - do you think of binary search it terms of how it is often presented - finding a value between two points, or more generally as a repeated division of an abstract search space.

Pete Kirkham
That doesn't work. Your four cases come down to two (t<m and t>m) which do exactly the same as in normal binary search.
Frank
Corrected the copy-paste error. Serves me right for being specific - a more descriptive version of the same thing got accepted.
Pete Kirkham
A: 

You just use a simple binary search as if it were a regular sorted array. The only trick is you need to rotate the array indexes:

(index + start-index) mod array-size

where the start-index is the offset of the first element in the circular array.

Christopher Barber
I think the problem with this is that you don't know the position of the smallest value in the array in advance, which is what you are calling start index. It would be fairly simple to find that value in log(n) time, though: Just check the first last and middle values in the array. If first is larger than middle, you know the jump is between them. And you can use the same trick on that half of the array to find it. If middle is larger than second, it is in the second half. If neither of these is true, then the zero position of the array (or current sub array) is the start-position.
PeterAllenWebb
+10  A: 

You can do this by taking advantage of the fact that the array is sorted, except for the special case of the pivot value and one of its neighbours.

  • Find the middle value of the array a.
  • If a[0] < a[mid], then all values in the first half of the array are sorted.
  • If a[mid] < a[last], then all values in the second half of the array are sorted.
  • Take the sorted half, and check whether your value lies within it (compare to the maximum idx in that half).
  • If so, just binary search that half.
  • If it doesn't, it must be in the unsorted half. Take that half and repeat this process, determining which half of that half is sorted, etc.
ire_and_curses
+1 Nice. I believe this is the answer the interviewers were looking for. It uses the fact that in any segment of the array, at least one half is sorted, so we can use it to decide which half to continue with, thus reducing the search space by 2 in each step, as required.
Eyal Schneider
Need distinct elements, else algorithm fails. And strangely, we can give a proof based on asymptotic runtime!
Moron
+1  A: 

guirgis: It is lame to post an interview question, guess you didn't get the job :-(

Use a special cmp function and you need only one pass with regular binary search. Something like:

def rotatedcmp(x, y):
  if x and y < a[0]:
    return cmp(x, y)
  elif x and y >= a[0]:
    return cmp(x, y)
  elif x < a[0]:
    return x is greater
  else:
    return y is greater

If you can depend on int underflow subtract a[0] - MIN_INT from each element as it is accessed and use regular compare.