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

定义


从一个简单问题说起

给定一个排序并不存在重复元素的数组: [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
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容

  • 文/洛小简 【单身狗】 我是一个狗,但并非一条或一只。吃了葡萄的人羡慕我的日子,我却垂涎着葡萄。世界真是太奇妙,追...
    洛小简阅读 484评论 8 3
  • 今天又刷了一遍《时空恋旅人》,不一样的感触,一样泪目。 雾霾蓝画质,温润空旷宁静美好。情节简单却不单调,没有跌宕起...
    十禾石阅读 686评论 0 4
  • 离人下酒何须愁,醉手抱缸驾晚舟。 一笑风轻吹旧夜,高歌沧海不回头。
    玩笑的熊阅读 266评论 64 22
  • 现在到了11点,妈妈,还有一个小时就回来了,我想了一会儿,决定帮妈妈做饭,可是 做什么呢?我在厨房里,左看右看,发...
    李君毅阅读 161评论 0 0
  • (一) 江湖是啥? 江湖是一个梦。 江湖是放不下的侠客梦。 我温华温大侠提着木剑冲入江湖,路上遇到一伙人:一个爷们...
    muer724阅读 406评论 4 0