ChunMinChang
2/5/2018 - 5:22 AM

Find an element in a increasing-order sorted array of n integers that has been rotated an unknown number of times. #searching #CtCI

Find an element in a increasing-order sorted array of n integers that has been rotated an unknown number of times. #searching #CtCI

#include <cassert>
#include <iostream>

//   Utilities
// ============================================================================
#define SIZE_OF_ARRAY(x) (sizeof(x)/sizeof(x[0]))

void printArray(int a[], int length) {
  for (int i = 0 ; i < length ; ++i) {
    std::cout << a[i] << " ";
  }
  std::cout << std::endl;
}

void shiftArray(int a[], int length) {
  int last = a[length - 1];
  for (int i = length - 1 ; i > 0 ; --i) {
    a[i] = a[i-1];
  }
  a[0] = last;
}

//   Binary search in a rotated sorted array
// ============================================================================
// The following algorithm fails when 2 2 2 2 3 4 2 or 2 3 4 2 2 2 2
// int findMaxIndex(int a[], int length)
// {
//   int left = 0;
//   int right = length - 1;
//   int middle = -1;

//   // Find the index of the 'last' max item.
//   while (left <= right) {
//     int middle = (left + right) / 2;
//     // Check if a[middle] is the last max item.
//     if (a[middle] > a[middle + 1]) { // a[middle] is the last max
//       return middle;
//     }
//     // Otherwise, keep searching the last max item.
//     if (a[middle] <= a[right]) { // middle to right is sorted, 
//       if (a[left] <= a[middle]) { // left to middle is sorted
//         // left to right is sorted!
//         assert(right == length - 1 || a[right] > a[right + 1]);
//         return right;
//       }
//       // left to middle is unsorted, so the max item is in left side!
//       assert(a[left] >= a[right]); // Make sure the max item is in left side.
//       right = middle - 1;
//     } else { // a[middle] > a[right] implies the max item is in right side!
//       // assert(a[left] <= a[middle]); // Check the left side is sorted.
//       left = middle + 1;
//     }
//   }

//   return -1;
// }

int findMaxIndex(int a[], int length, int left, int right)
{
  assert(left >= 0 && right < length);
  if (left > right) {
    return -1;
  }
  int middle = (left + right) / 2;
  if (a[middle] > a[middle + 1]) { // the middle is the last max item!
    return middle;
  }
  if (a[middle] < a[right]) { // middle to right is strictly sorted.
    if (a[left] <= a[middle]) { // left to middle is sorted.
      // then the max item must be in the rightmost!
      return right;
    }
    // the max item must be in left side since the left to middle is unsorted.
    return findMaxIndex(a, length, left, middle - 1);
  }
  if (a[middle] > a[right]) { // middle to right is unsorted,
    // so left to middle must be sorted. Find the max item in the right side.
    return findMaxIndex(a, length, middle + 1, right);
  }
  // when a[middle] == a[right]:
  // if a[middle] != a[left] only left side are different.
  // There are two possibilities: 3 4 5 2 2 2 2 or 3 4 5 6 6 6 6
  if (a[middle] > a[left]) { // 3 4 5 6 6 6 6
    // then the max item must be in the rightmost!
    return right;
  }
  if (a[middle] < a[left]) { // 3 4 5 2 2 2 2
    return findMaxIndex(a, length, left, middle - 1);
  }
  // if a[middle] == a[left]: the max item can be in both side
  // (e.g., 2 2 2 2 3 4 2 or 2 3 4 2 2 2 2).
  int index = findMaxIndex(a, length, left, middle - 1); // Find in left first.
  if (index != -1) { // if the max item is in left, return it directly.
    return index;
  }
  // find the max item in the right side if it's not in the left side.
  return findMaxIndex(a, length, middle + 1, right);
}

int findOffset(int a[], int length)
{
  // int index = findMaxIndex(a, length);
  int index = findMaxIndex(a, length, 0, length - 1);
  return (index + 1) % length;
}

int rotatedIndex(int index, int offset, int length)
{
  return (index + offset) % length;
}

int binarySearch(int a[], int length, int offset, int target)
{
  int left = 0;
  int right = length - 1;
  int middle = -1;

  while(left <= right) {
    int middle = (left + right) / 2;
    int rotatedMid = rotatedIndex(middle, offset, length);
    if(a[rotatedMid] == target) {
      return rotatedMid;
    }
    if (a[rotatedMid] < target) {
      left = middle + 1; // Search right
    } else {
      right = middle - 1; // Search left
    }
  }
  return -1;
}

// Actually, we could apply the idea from recursive findMaxIndex
// to search the target directly.
int binarySearch(int a[], int length, int left, int right, int target)
{
  assert(left >= 0 && right < length);
  if (left > right) {
    return -1;
  }
  int middle = (left + right) / 2;
  if (a[middle] == target) {
    return middle;
  }
  // when the middle is not the target:
  if (a[middle] < a[right]) { // middle to right is strictly sorted.
    // if middle < target <=right, then search it on right side.
    if (a[middle] < target && target <= a[right]) {
      return binarySearch(a, length, middle + 1, right, target);
    }
    // search target on left side.
    return binarySearch(a, length, left, middle - 1, target);
  }
  if (a[middle] > a[right]) { // middle to right is unsorted,
    // so left to middle must be sorted.
    // if left <= target < middle, then search it on left side.
    if (a[left] <= target && target < a[middle]) {
      return binarySearch(a, length, left, middle - 1, target);
    }
    // search it on right side.
    return binarySearch(a, length, middle + 1, right, target);
  }
  // if a[middle] == a[right]:
  if (a[middle] != a[left]) { // if the values change between middle to right,
    // then the left must be same as middle.
    // Thus, if left is different from middle, all the values from middle to
    // right must be same. Only left side have differences, so search on left
    // side.(the middle is different from target, so all the same values from
    // middle to right are also different from target.)
    return binarySearch(a, length, left, middle - 1, target);
  }
  // if a[left] == a[middle] == a[right]: 2 3 4 2 2 2 2 or 2 2 2 2 3 4 2.
  // The target may be in both side.
  // Search on left side first.
  int index = binarySearch(a, length, left, middle - 1, target);
  if (index != -1) { // if target is found on left side, return it directly.
    return index;
  }
  return binarySearch(a, length, middle + 1, right, target);
}

int binarySearch(int a[], int length, int target)
{
  // int offset = findOffset(a, length);
  // return binarySearch(a, length, offset, target);
  return binarySearch(a, length, 0, length - 1, target);
}

int binarySearch(int a[], int length, int target)
{
  return binarySearch(a, length, 0, length - 1, target);
}

//   Test
// ============================================================================
bool sorted(int a[], int length)
{
  for (int i = 0 ; i < length-1 ; ++i) {
    if (a[i] > a[i+1]) {
      return false;
    }
  }
  return true;
}

int* allocateTable(int a[], int length)
{
  int min = a[0] - 1;
  int max = a[length - 1] + 1;
  int len = max - min + 1;
  if (!length) {
    return nullptr;
  }
  int* table = new int[len];
  for (int i = 0 ; i < len ; ++i) {
    table[i] = -1;
  }
  for (int i = 0 ; i < length ; ++i) {
    table[a[i] - min] = i;
  }
  return table;
}

void test(int a[], int length)
{
  assert(length);
  assert(sorted(a, length));

  int min = a[0];
  int max = a[length - 1];
  int rotateTimes = length;
  if (min == max) { 
    // if all the array elements are same, 
    // then we only need to test once!
    rotateTimes = 1;
  }

  int* table = allocateTable(a, length);
  assert(table);

  min = min - 1;
  max = max + 1;
  int len = max - min + 1;

  for (int shift = 0 ; shift < rotateTimes ; ++shift) {
    std::cout << "offset: " << shift << std::endl;
    printArray(a, length);
    // Check findOffset
    assert(findOffset(a, length) == shift);
    // Check binarySearch
    for(int target = min ; target <= max ; ++target) {
      int index = binarySearch(a, length, target);
      int answer = table[target - min];
      std::cout << "find " << target << " at " << index << " (expect found: "
                << ((answer != -1) ? "Yes" : "No") << std::endl;
      assert((index == -1) == (answer == -1));
    }
    shiftArray(a, length);
  }
  delete[] table;
}

//   Main Program
// ============================================================================
int main()
{
  int a1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
  test(a1, SIZE_OF_ARRAY(a1));

  int a2[] = { 3, 3, 3, 6, 7, 7, 7, 11, 12, 15, 15 };
  test(a2, SIZE_OF_ARRAY(a2));

  int a3[] = { -5, -2, -2, -2, 4, 4, 4, 4, 10, 10, 10, 13, 13, 14 };
  test(a3, SIZE_OF_ARRAY(a3));

  int a4[] = { 7, 7, 7, 7, 7 };
  test(a4, SIZE_OF_ARRAY(a4));

  int a5[] = { 2, 2, 2, 2, 2, 3, 4 };
  test(a5, SIZE_OF_ARRAY(a5));

  int a6[] = { 2, 3, 4, 4, 4, 4, 4 };
  test(a6, SIZE_OF_ARRAY(a6));

  return 0;
}