算法 - 二分查找

基础二分

   /**
     * 要求数组有序 ,二分法
     * @param arr
     * @param
     * @return
     */
    private static int binarySearch(int[] arr, int left, int right, int findVal) {
        //给一个出口
        if (left >= right) {
            return -1;
        }

        int mid = (left + right) / 2;
        int midV = arr[mid];

        if (midV < findVal) {
            return binarySearch(arr, mid + 1, right, findVal);
        } else if (midV > findVal) {
            return binarySearch(arr, left, mid - 1, findVal);
        } else {
            return mid;
        }

    }

这里有一个讲究的地方:

如果用mid=(left+right)/2,在运行二分查找程序时可能溢出超时。
因为如果left和right相加超过int表示的最大范围时就会溢出变为负数。
所以如果想避免溢出,不能使用mid=(left+right)/2,应该使用mid=left+(right-left)/2。

还有的方式:

    public int search(int[] nums, int pivot) {
        int length = nums.length;
        int left = 0;
        int right = length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == pivot) return mid;
            if (nums[mid] > pivot) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return -1;
    }

同时要注意 left<= right 的退出条件,不然存在以下直接退出-1情况:

search(new int[]{1, 2, 3, 4, 5,6}, 4)

寻找旋转排序数组中的最小值

假设按照升序排序的数组在预先未知的某个点上进行了旋转。例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] 。
请找出其中最小的元素。

本题的关键为找间隙,找到那个进行的旋转的间隙位置,其左右就是最小值。

不断逼近间隙,

 public int findMin(int[] nums) {
        int left = 0;
        int right = nums.length - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] > nums[right]) {          
                left = mid + 1;
            } else {                                
                right = mid;
            }
        }
        return nums[left];
    }

存在一种找到即退出的优化方案:

 public int findMin(int[] nums) {

    if (nums.length == 1) {
      return nums[0];
    }
    int left = 0, right = nums.length - 1;
    if (nums[right] > nums[0]) {
      return nums[0];
    }
    while (right >= left) {
      int mid = left + (right - left) / 2;
      if (nums[mid] > nums[mid + 1]) {
        return nums[mid + 1];
      }
      if (nums[mid - 1] > nums[mid]) {
        return nums[mid];
      }  
      if (nums[mid] > nums[0]) {
        left = mid + 1;
      } else {
        right = mid - 1;
      }
    }
    return -1;
  }

存在重复数据

解析来自leetcode
算法 - 二分查找
第一种情况是nums[pivot] < nums[high]。如下图所示,这说明nums[pivot] 是最小值右侧的元素,因此我们可以忽略二分查找区间的右半部分。

算法 - 二分查找
第二种情况是 nums[pivot]>nums[high]。如下图所示,这说明 nums[pivot] 是最小值左侧的元素,因此我们可以忽略二分查找区间的左半部分。
算法 - 二分查找

第三种情况是nums[pivot]==nums[high]。如下图所示,由于重复元素的存在,我们并不能确定 nums[pivot] 究竟在最小值的左侧还是右侧,因此我们不能莽撞地忽略某一部分的元素。我们唯一可以知道的是,由于它们的值相同,所以无论 nums[high] 是不是最小值,都有一个它的「替代品」nums[pivot],因此我们可以忽略二分查找区间的右端点。
算法 - 二分查找

    public int findMin(int[] nums) {
        int low = 0;
        int high = nums.length - 1;
        while (low < high) {
            int pivot = low + (high - low) / 2;
            if (nums[pivot] < nums[high]) {
                high = pivot;
            } else if (nums[pivot] > nums[high]) {
                low = pivot + 1;
            } else {
                high -= 1;
            }
        }
        return nums[low];
    }
上一篇:Python实现各种排序算法


下一篇:Java 性能瓶颈分析工具 你知道几个?