题目

搜索旋转排序数组 - 图1

解题思路

二分查找

对于有序数组,可以使用二分查找的方法查找元素。

但是这道题中,数组本身不是有序的,进行旋转后只保证了数组的局部是有序的,但是依旧可以进行二分查找。可以发现的是,我们将数组从中间分开成左右两部分的时候,一定有一部分的数组是有序的。

对于[4,5,6,7,0,1,2]这个例子,我们从 6 这个位置分开以后数组变成了 [4, 5, 6] 和 [7, 0, 1, 2] 两个部分,其中左边 [4, 5, 6] 这个部分的数组是有序的,其他也是如此。

这启示我们可以在常规二分查找的时候查看当前 mid 为分割位置分割出来的两个部分 [l, mid] 和 [mid + 1, r] 哪个部分是有序的,并根据有序的那个部分确定我们该如何改变二分查找的上下界,因为我们能够根据有序的那部分判断出 target 在不在这个部分:

  • 如果 [l, mid - 1] 是有序数组,且 target 的大小满足[nums[l],nums[mid]),则我们应该将搜索范围缩小至 [l, mid - 1],否则在 [mid + 1, r] 中寻找。
  • 如果 [mid, r] 是有序数组,且 target 的大小满足 (\textit{nums}[mid+1],\textit{nums}[r]](nums[mid+1],nums[r]],则我们应该将搜索范围缩小至 [mid + 1, r],否则在 [l, mid - 1] 中寻找。

需要注意的是,二分的写法有很多种,所以在判断 target 大小与有序部分的关系的时候可能会出现细节上的差别。

代码

  1. class Solution {
  2. public int search(int[] nums, int target) {
  3. int n = nums.length;
  4. if (n == 0) {
  5. return -1;
  6. }
  7. if (n == 1) {
  8. return nums[0] == target ? 0 : -1;
  9. }
  10. int l = 0, r = n - 1;
  11. while (l <= r) {
  12. int mid = (l + r) / 2;
  13. if (nums[mid] == target) {
  14. return mid;
  15. }
  16. if (nums[0] <= nums[mid]) {
  17. if (nums[0] <= target && target < nums[mid]) {
  18. r = mid - 1;
  19. } else {
  20. l = mid + 1;
  21. }
  22. } else {
  23. if (nums[mid] < target && target <= nums[n - 1]) {
  24. l = mid + 1;
  25. } else {
  26. r = mid - 1;
  27. }
  28. }
  29. }
  30. return -1;
  31. }
  32. }