美文网首页
算法学习(六): 二分搜索

算法学习(六): 二分搜索

作者: squall1744 | 来源:发表于2018-06-09 15:39 被阅读0次

    定义


    从一个简单问题说起

    给定一个排序并不存在重复元素的数组: [1,2,5,7,8,9,13], 查找8的位置

    暴力解法: 遍历整个数组, 找到与给定值相同的元素, 返回下标, 时间复杂度O(n)

    另一种解法:

    1. 我们可以先取数组中间位置的值, 看中间位置的值和目标值的大小, 假如中间位置的值大于目标值, 则说明目标值处于数组的中间位置的左半部分, 假如中间位置的值小于目标值, 则说明目标值处于数组中间位置的右半部分
    2. 这里我们假设数组中间位置的值大于目标值, 则说明目标值处于数组中间值的左半部分, 那么右半部分我们就不需要再去查找了
    3. 对于中间值的左半部分我们继续取中间位置的值, 像上面那样我们继续判断中间位置的值的大小, 大于目标值我们取左半部分, 小于目标值我们取右半部分
    4. 按照上面每次去一半的方法一直分下去, 直到数组中的某个位置值与目标值相等, 返回该位置

    时间复杂度: 由于我们每次分割都会少掉一半, 所以时间复杂度为O(logn)

    二分搜索

    上面这种每次取一半搜索的方法就是二分搜索

    二分搜索注意事项

    • 对输入做异常处理: 数组为空或者数组长度为0
    • mid = start + (end - start) / 2 这种表示方法可以防止两个整型值相加时溢出
    • 使用迭代而不是递归进行二分查找, 因为工程中递归写法存在潜在溢出的可能
    • while终至条件: start + 1 < end而不是start <= end, start == end时可能出现死循环
    • 迭代终至时target应为start或者end中的一个。循环终至条件有两个, 具体应看是找到第一个还是最后一个而定
    function binarySearch(arr, target) {
      if (arr.length < 1) {
        return -1
      }
      let start = 0
      let end = arr.length - 1
      while (start + 1 < end) {
        let mid = Math.floor(start + (end - start) / 2)
        if (arr[mid] >= target) {
          end = mid
        }else if (arr[mid] < target) {
          start = mid
        }
      }
      
      if (arr[start] === target) {
        return start
      }
      if (arr[end] === target) {
        return end
      }
    return -1
    }
    

    例题


    搜索插入位置

    给定一个排序数组和一个目标值, 如果在数组中找到了目标值则返回索引。
    如果没有, 返回到它将会被按顺序插入的位置。
    你可以假设在数组中无重复元素

    case1:
    输入: [1,3,5,6], 5 输出: 2
    case2:
    输入: [1,3,5,6], 2 输出: 1
    case3:
    输入: [1,3,5,6], 7 输出: 4
    case4:
    输入: [1,3,5,6], 0 输出: 0

    分析:

    • 在目标值是数组中的数时, 很明显这就是标准的二分搜索的题
    • 在目标值不是数组中的数时, 情况分三种情况:
      1. 目标值在数组中某两个数大小之间, 上面题的case2就是这种情况, 这种情况下, 目标值的大小一定是处在数组某相邻两个数之间, 换句话说也就是目标值永远插入在start+1的位置
      2. 目标值比数组起始位置数还小, case4, 这种情况永远返回0
      3. 目标值比数组最后一位的值还大. case3, 此时永远返回arr.length + 1
    function searchInsert(arr, target) {
      if (arr.length < 1) {
        return 0
      }
      
      let start = 0
      let end = arr.length - 1
      
      while (start + 1 < end) {
        let mid = Math.floor(start + (end - start) / 2)
        if (arr[mid] < target) {
          start = mid
        } else {
          end = mid
        }
      }
      if (arr[start] === target) {
        return start
      }
      if (arr[end] === target) {
        return end
      }
      if (target > arr[end]) {
        return end + 1
      }
      if (target < arr[start]) {
        return 0
      }
      return start + 1
    }
    

    搜索二维矩阵

    编写一个高效的算法来搜索m*n矩阵中的一个目标值。
    该矩阵具有以下特性:
    每行中的整数从左向右排序。
    每行的第一个数大于前一行的最后一个整数。

    例如:
    以下矩阵:
    1   2   3   4
    5   6   7   8
    11 13 15 17
    56 78 89 98
    给定一个目标值3, 返回下标

    分析:

    • 由于每一行第一个数都比前一行最后一个数大, 所有这个矩阵转换成一维数组的话, 是一个排好序的一维数组, 这个问题就可以转换成一维数组搜索, 也就是最基本的二维数组问题
    • 矩阵每一行4个元素, 第二行第一个matrix[1][0]转换成一维数组时下标为1 * 4 + 0 = 4
      矩阵第三行第二个matrix[2][1]转换成一维数组时下标为 2 * 4 + 1 = 9
      以此类推, matrix[n][m]转换成一维数组时下标为n * 4 + m
      一维数组arr[n]转换成每组有m个数的martix时下标为matrix[n/m][n%m]
    function binarySearch(matrix, target) {
      if (matrix.length < 1 || matrix[0].length < 1) {
        return -1
      }
      
      let start = 0
      let end = matrix.length * matrix[0].length - 1
    
      while (start + 1 < end) {
        let mid = Math.floor(start + (end - start) / 2)
        if (matrix[Math.floor(mid / matrix[0].length)][mid % matrix[0].length] < target) {
          start = mid
        } else {
          end = mid
        }
      }
      if (matrix[Math.floor(start / matrix[0].length)][start % matrix[0].length] === target) {
        return [Math.floor(start / matrix[0].length), start % matrix[0].length]
      }
      if (matrix[Math.floor(end / matrix[0].length)][end % matrix[0].length] === target) {
        return [Math.floor(end / matrix[0].length), end % matrix[0].length]
      }
      return -1
    }
    

    求整数的平方根

    给定一个整数, 返回它的平方根, 平方根不是整数的, 向下取整
    例如:
    输入: 25 输出: 5
    输入: 0 输出: 0
    输入: 9 输出: 3
    输入: 7 输出: 2

    分析:
    这道题可以用二分搜索来做

    • 取0到n中间的数m
    • m的平方大于n的话说明, n的平方根在0到m之间
    • 去0到m中间的值, 继续平方, 如果大于n, 说明n的平方根在0到m/2之间, 如果小于m, 说明在m/2到m之间
    • 依次类推, 直到找到结果, 如果分到没办法再分下去还没有找到结果, 则返回最后一次取值范围的左边界
    function mySqrt(n) {
     if (n === 0) {
       return 0
     }else if (n < 0) {
       return 'error'
     }
     
     let start = 0
     let end = n
     while (start + 1 < end) {
       let mid = Math.floor(start + (end - start) / 2)
       if (mid * mid < n) {
         start = mid
       } else {
         end = mid
       }
     }
     if (end === n / end) {
       return end
     }
     return start
    }
    
    

    相关文章

      网友评论

          本文标题:算法学习(六): 二分搜索

          本文链接:https://www.haomeiwen.com/subject/tpbosftx.html