算法系列--二分查找

二分查找是一种效率较高的查找方法,前提是数据结构必须先排好序,它的平均时间复杂度是O(logn)。碰到排好序的数组,第一时间应该想到用二分法求解。

二分查找的递归与非递归实现

  1. 在有序数组arr中,查找目标值value的元素下标
// 递归
function biSearch(arr, start, end, value) {
  if (start > end) return -1
  let mid = Math.floor((start + end) / 2)
  if (arr[mid] < value) {
    return biSearch(arr, mid + 1, end, value)
  } else if (arr[mid] > value) {
    return biSearch(arr, start, mid - 1, value)
  } else {
    return mid
  }
}
// 非递归
function biSearch(arr, value) {
  let start = 0
  let end = arr.length - 1
  let mid = (start + end) >> 1
  while (start <= end) {
    if (arr[mid] === value) {
      return mid
    }
    if (arr[mid] < value) {
      start = mid + 1
    } else {
      end = mid - 1
    }
    mid = (start + end) >> 1
  }
  return -1
}

二分查找变形问题

  1. 在有序数组中,查找第一个等于给定值的元素的下标
// 遍历
function getFirstOne(arr, value) {
  let start = 0
  let end = arr.length - 1
  let mid = (start + end) >> 1
  while (start <= end) {
    if (arr[mid] > value) {
      end = mid - 1
    } else if (arr[mid] < value) {
      start = mid + 1
    } else {
      if (mid === start || arr[mid - 1] !== value) {
        return mid
      } else {
        end = mid - 1
      }
    }
    mid = (start + end) >> 1
  }
  return -1
}

let arr = [2,2,2,3,3,4,5,6,6,7]
let value = 10
let result = getFirstOne(arr, value)
console.log('result', result)
// 递归
// 递归
function getFirstOne(arr, start, end, value) {
  if (start > end) return -1
  let mid = (start + end) >> 1
  if (arr[mid] > value || (mid !== start && arr[mid-1] === value)) {
    return getFirstOne(arr, start, mid - 1, value)
  } else if (arr[mid] < value) {
    return getFirstOne(arr, mid + 1, end, value)
  } else {
    return mid
  }
}
let arr = [2,2,2,3,3,4,5,6,6,7]
let value = 3
let result = getFirstOne(arr, 0, arr.length - 1, value)
console.log('result', result)
  1. 在有序数组中,查找最后一个等于给定值的元素的下标
// 遍历
function getLastOne(arr, value) {
  let start = 0
  let end = arr.length - 1
  let mid = (start + end) >> 1
  while (start <= end) {
    if (arr[mid] < value) {
      start = mid + 1
    } else if (arr[mid] > value) {
      end = mid - 1
    } else {
      if (mid === end || arr[mid+1] !== value) {
        return mid
      } else {
        start = mid + 1
      }
    }
    mid = (start + end) >> 1
  }
  return -1
}
let arr = [2,2,2,3,3,4,5,6,6,7]
let value = 3
let result = getLastOne(arr, value)
console.log('result', result)
// 递归
function getLastOne(arr, start, end, value) {
  if (start > end) return -1
  let mid = (start + end) >> 1
  if (arr[mid] < value) {
    return getLastOne(arr, mid + 1, end, value)
  } else if (arr[mid] > value) {
    return getLastOne(arr, start, mid - 1, value)
  } else {
    if (mid === end || arr[mid + 1] !== value) {
      return mid
    } else {
      return getLastOne(arr, mid + 1, end, value)
    }
  }
}
let arr = [2,2,2,3,3,4,5,6,6,7]
let value = 3
let result = getLastOne(arr, 0, arr.length - 1, value)
console.log('result', result)
  1. 在有序数组中,查找第一个大于等于给定值的元素的下标
// 遍历
function getFirstMaxOne(arr, value) {
  let start = 0
  let end = arr.length - 1
  let mid = (start + end) >> 1
  while (start <= end) {
    if (arr[mid] < value) {
      start = mid + 1
    } else {
      if (arr[mid - 1] < value || mid === start) {
        return mid
      }
      end = mid - 1
    }
    mid = (start + end) >> 1
  }
  return -1
}
let arr = [2,2,2,3,3,4,5,6,6,7]
let value = 6
let result = getFirstMaxOne(arr, value)
console.log('result', result)
// 递归
function getFirstMaxOne(arr, start, end, value) {
  if (start > end) return -1
  let mid = (start + end) >> 1
  if (arr[mid] < value) {
    return getFirstMaxOne(arr, mid + 1, end, value)
  } else {
    if (arr[mid-1] < value || mid === start) {
      return mid
    }
    return getFirstMaxOne(arr, start, mid - 1, value)
  }
}
let arr = [2,2,2,3,3,4,5,6,6,7]
let value = 2
let result = getFirstMaxOne(arr, 0, arr.length - 1, value)
console.log('result', result)
  1. 在有序数组中,查找最后一个小于等于给定值的元素的下标
// 遍历
function getFirstMaxOne(arr, value) {
  let start = 0
  let end = arr.length - 1
  let mid = (start + end) >> 1
  while (start <= end) {
    if (arr[mid] < value) {
      start = mid + 1
    } else {
      if (arr[mid - 1] < value || mid === start) {
        return mid
      }
      end = mid - 1
    }
    mid = (start + end) >> 1
  }
  return -1
}
let arr = [2,2,2,3,3,4,5,6,6,7]
let value = 6
let result = getFirstMaxOne(arr, value)
console.log('result', result)
// 递归
function getLastMinOne(arr, value) {
  let start = 0
  let end = arr.length - 1
  let mid = (start + end) >> 1
  while (start <= end) {
    if (arr[mid] > value) {
      end = mid - 1
    } else {
      if (arr[mid + 1] > value || mid === end) {
        return mid
      }
      start = mid + 1
    }
    mid = (start + end) >> 1
  }
  return -1
}
let arr = [2,2,2,3,3,4,5,6,6,7]
let value = 2
let result = getLastMinOne(arr, value)
console.log('result', result)

其它经典试题

  1. 在有序的稀疏数组中,查找目标值
function findStr(words, s) {
  let start = 0
  let end = words.length - 1
  let mid = (start + end) >> 1
  while (start <= end) {
    // 遇到特殊字符,退化成线性
    while (words[mid] === '' && mid > start) {
      mid = mid - 1
    }
    if (words[mid] === s) {
      return mid
    }
    if (words[mid] > s) {
      end = mid - 1
    } else {
      start = mid + 1
    }
    mid = (start + end) >> 1
  }
  return -1
}
  1. 在有序的数组中,查找确实的数字
// 缺失数字
function missNum(nums) {
  let start = 0
  let end = nums.length - 1
  while (start <= end) {
    let mid = (start + end) >> 1
    if (nums[mid] <= mid) {
      start = mid + 1
    } else {
      if (nums[mid-1] === mid - 1 || mid === start) {
        return mid
      }
      end = mid - 1
    }
  }
  return nums.length
}
let nums = [1,2,3,4,5,6,7]
let result = missNum(nums)
console.log('result', result)
  1. 在有序的数组中,统计目标值出现的次数
function search(nums, target) {
  let start = 0
  let end = nums.length - 1
  let times = 0
  while (start <= end) {
    let mid = (start + end) >> 1
    let temp = mid
    if (nums[mid] < target) {
      start = mid + 1
    } else if (nums[mid] > target) {
      end = mid - 1
    } else {
      // 左边统计
      while (temp >= start && nums[temp] === target) {
        times++
        temp--
      }
      // 右边统计
      temp = mid + 1
      while(temp <= end && nums[temp] === target) {
        times++
        temp++
      }
      break
    }
  }
  return times
}
let nums = [9, 9, 10, 11, 12, 12, 12, 12]
let result = search(nums, 12)
console.log('result', result)
  1. 查找第K小的元素
    leetcode378
// 查找第K小的元素,二维矩阵 O(nlogn)
function fn2(matrix, k) {
  let n = matrix.length
  if (k < 0 || k > n * n) return null
  let start = matrix[0][0]
  let end = matrix[n-1][n-1]
  while (start < end) {
    let mid = (start + end) >> 1
    let num = getNum(matrix, mid)
    if (num >= k) {
      end = mid
    } else {
      start = mid + 1
    }
  }
  return start
}

// 二维矩阵中,获取小于目标值的数量 O(n)
function getNum(matrix, value) {
  let n = matrix.length
  let i = n - 1
  let j = 0
  let num = 0
  while(i >= 0 && j < n) {
    if (matrix[i][j] <= value) {
      num = num + i + 1
      j++
    } else {
      i--
    }
  }
  return num
}

总结

二分查找问题很常见,碰到关键字有序数组时间复杂度O(logn),就要想到用二分查找;具体实现可以用遍历或者递归,多练习就能熟能生巧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值