面试题:二分法代码实现求一个数的平方根

2020-01-17  本文已影响0人  秋天的铁工匠

先思考一个开发场景下的问题。

这里有个游戏的例子

image.png

为了方便理解,我们假设只有 10 个订单,订单金额分别是:8,11,19,23,27,33,45,55,67,98。

还是利用二分思想,每次都与区间的中间数据比对大小,缩小查找区间的范围。为了更加直观,我画了一张查找过程的图。其中,low 和 high 表示待查找区间的下标,mid 表示待查找区间的中间元素下标。

image.png

总结: 二分查找针对的是一个有序的数据集合,查找思想有点类似分治思想。每次都通过跟区间的中间元素对比,将待查找的区间缩小为之前的一半,直到找到要查找的元素,或者区间被缩小为 0。

上面的例子来自王争老师


关于二分法的几个问题


public int bsearch(int[] a, int n, int value) {
  int low = 0;
  int high = n - 1;

  while (low <= high) {
    int mid = low+((high-low) >>1);
    if (a[mid] == value) {
      return mid;
    } else if (a[mid] < value) {
      low = mid + 1;
    } else {
      high = mid - 1;
    }
  }

  return -1;
}

这段代码有三个地方需要注意

low <= high 而不是 low<high
  int mid = low+((high-low) >>1)不能错写成   int mid = low+(high-low) >>1; 因为 运算符的优先级问题。
low=mid+1,high=mid-1。注意这里的 +1 和 -1,如果直接写成 low=mid 或者 high=mid,就可能会发生死循环。


// 二分查找的递归实现
public int bsearch(int[] a, int n, int val) {
  return bsearchInternally(a, 0, n - 1, val);
}

private int bsearchInternally(int[] a, int low, int high, int value) {
  if (low > high) return -1;

  int mid =  low + ((high - low) >> 1);
  if (a[mid] == value) {
    return mid;
  } else if (a[mid] < value) {
    return bsearchInternally(a, mid+1, high, value);
  } else {
    return bsearchInternally(a, low, mid-1, value);
  }
}

如何使用二分法和牛顿迭代法实现”求一个数的平方根的实现"

package com.dc.canal.example;

import java.math.BigDecimal;

/**
 * @ClassName SqrtUtil
 * @Description TODO
 * @Author fangxianyang
 * @Date 2020/1/1711:05 上午
 **/
public class SqrtUtil {
    /**
     * 方法1 精确度差
     * @param data
     * @return
     */
    public static double getSquareRoot(int data){
        if(data < 0){
            return -1;
        }
        double low = 0.0;
        double high = data;
        double mid = 0.0;
        while(isNumOfDigLessThenInput(mid, 6) ){
            mid = low + ((high - low) / 2);
            if(data == mid*mid){
                return mid;
            }else if(data < mid*mid){
                high = mid;
            }else{
                low = mid;
            }
        }
        return mid;
    }

    private static boolean isNumOfDigLessThenInput(double data,int num){
        String dataStr = String.valueOf(data);
        int index = dataStr.indexOf(".");
        if(index == -1){
            return true;
        }
        int numofDig = dataStr.length() - index;
        return numofDig <= 6;
    }

    /**
     * 方法2,易于理解,精确度高
     * @param a
     * @return
     */
    public static Double squareRoot(int a){
        double x = 0;
        double low = 0;
        double high = a;
        while(low<=high){
            x = (low+high)/2;
            if(x>a/x){
                high = x-0.000001;
            }
            //防止溢出
            if(x<a/x){
                low = x+0.000001;
            }
            if(x==a/x){
                //刚好整除
                return x+0.000001;
            }
        }
        //精确到六位小数
        return new BigDecimal(x).setScale(6, BigDecimal.ROUND_HALF_UP).doubleValue();
    }

    public static void main(String[] args) {
        System.out.println( SqrtUtil.squareRoot(5));
    }
}


代码结果:

image

二分法的时间复杂度是多少

image

为什么二分法使用数组作为数据结构,链表可以吗?

如何快速定位IP对应的省份地址?

第一种:查找第一个值等于给定值的元素

public int bsearch(int[] a, int n, int value) {
  int low = 0;
  int high = n - 1;
  while (low <= high) {
    int mid =  low + ((high - low) >> 1);
    if (a[mid] > value) {
      high = mid - 1;
    } else if (a[mid] < value) {
      low = mid + 1;
    } else {
      if ((mid == 0) || (a[mid - 1] != value)) return mid;
      else high = mid - 1;
    }
  }
  return -1;
}

第二种:查找最后一个值等于给定值的元素


public int bsearch(int[] a, int n, int value) {
  int low = 0;
  int high = n - 1;
  while (low <= high) {
    int mid =  low + ((high - low) >> 1);
    if (a[mid] > value) {
      high = mid - 1;
    } else if (a[mid] < value) {
      low = mid + 1;
    } else {
      if ((mid == n - 1) || (a[mid + 1] != value)) return mid;
      else low = mid + 1;
    }
  }
  return -1;
}

第三种:查找第一个大于等于给定值的元素


public int bsearch(int[] a, int n, int value) {
  int low = 0;
  int high = n - 1;
  while (low <= high) {
    int mid =  low + ((high - low) >> 1);
    if (a[mid] >= value) {
      if ((mid == 0) || (a[mid - 1] < value)) return mid;
      else high = mid - 1;
    } else {
      low = mid + 1;
    }
  }
  return -1;
}

第四种:查找最后一个小于等于给定值的元素


public int bsearch7(int[] a, int n, int value) {
  int low = 0;
  int high = n - 1;
  while (low <= high) {
    int mid =  low + ((high - low) >> 1);
    if (a[mid] > value) {
      high = mid - 1;
    } else {
      if ((mid == n - 1) || (a[mid + 1] > value)) return mid;
      else low = mid + 1;
    }
  }
  return -1;
}

对于我们的问题可以使用第四种变形实现 快速定位IP对应的省份地址问题

如果有序数组是一个循环有序数组,比如 4,5,6,1,2,3。针对这种情况,如何实现一个求“值等于给定值”的二分查找算法呢?

class Solution {
    public int search(int[] nums, int target) {
        int left = 0;
        int right = nums.length-1;
        int mid = left + (right-left)/2;

        while(left <= right){
            if(nums[mid] == target){
                return mid;
            }

            if(nums[left] <= nums[mid]){  //左边升序
                if(target >= nums[left] && target <= nums[mid]){//在左边范围内
                    right = mid-1;
                }else{//只能从右边找
                    left = mid+1;
                }

            }else{ //右边升序
                if(target >= nums[mid] && target <= nums[right]){//在右边范围内
                    left = mid +1;
                }else{//只能从左边找
                    right = mid-1;
                }

            }
            mid = left + (right-left)/2;
        }

        return -1;  //没找到
    }
}

欢迎关注“会讲历史的程序员”公众号,一起学习。

上一篇 下一篇

猜你喜欢

热点阅读