二分查找(golang)
感觉自己二分总是写不对,上下界的寻找出错,在此记录下
先是基本的查找和特殊查找,然后是对二分进行融合的题目
2022.8.9更新 : 全部使用y总模板即可.清晰又好用.
y总二分模板和原理,超级好用
目录
相关学习链接
y总二分模板和原理,超级好用
b乎关于二分的讨论
循环带==号的解法,别搞混了
还有这个
教程可以试读
本文是基于for left<right,而非等于的方式
普通二分
func search(nums []int, target int) int {
left,right:=0,len(nums)
for left<right{ //相等时候退出循环
mid:=left+(right-left)/2
if nums[mid]<target{ //左闭右开,所以左边+1
left = mid+1
}else if nums[mid]>target{
right = mid //右边不变,右开,不包含在内
}else{
return mid
}
}
return left //目标值如果不存在left,则是大于目标值的第一位元素下标
}
704 二分查找
- 简单二分,无重复找target, 标准写法
这样写的好处,最后如果目标不存在,直接返回left,则left是大于目标值的下一位下标
这样的写法需要考虑的问题是,1.返回的left元素不一定等于目标值,2.返回的left已经越界到达最右边
func search(nums []int, target int) int {
left,right:=0,len(nums)
for left<right{ //相等时候退出循环
mid:=left+(right-left)/2
if nums[mid]<target{ //左闭右开,所以左边+1
left = mid+1
}else if nums[mid]>target{
right = mid //右边不变,右开,不包含在内
}else{
return mid
}
}
return -1 //没找到返回-1,如果返回left,则是大于目标值的第一位元素下标
}
69 x的平方根
func mySqrt(x int) int {
//二段性,看符合条件的值偏向哪一部分
l,r:=0,x
for l<r{
mid:=l+(r-l+1)/2
if mid*mid<=x{ //小于等于的最右边,模板二 ,因为根号8的值为2,则是实际结果的左侧
l = mid
}else{
r = mid-1
}
}
return l
}
x平方根(保留3位小数)
- 核心思想一样,移动的时候每次移动1e-3
func Sqrt(x float64)float64{ //找符合条件的左侧
l,r:=0.0,x
for l<r{
mid:=l+(r-l+1e-3)/2 //+1e-3避免死循环
if mid*mid<=x{
l = mid
}else{
r = mid-1e-3
}
}
return l
}
35 搜索插入位置
- 同上面一样,但开始需要增加特殊判断,当目标值1大于数组值
(此题目数组中无重复值,如果有,代码需要改动)
为什么要特殊判断,举个栗子
输入数组[1,3,5,6] 找7应该插入的位置
按照之前代码,left会不断=mid+1,向右,right一直在len(nums),也就是3
最终 left==right时候退出循环,left ==3,
实际应该插入的位置下标为4,
因此先增加判断,target>最后一位,直接 return 长度(就是应该插入的位置下标)
func searchInsert(nums []int, target int) int {
//特殊判断,最后一位和目标值
if target>nums[len(nums)-1]{
return len(nums)
}
//寻找大于目标值下标
left,right:=0,len(nums)-1
for left<right{
mid:=left+(right-left)/2
if nums[mid]<target{
left = mid+1
}else{
right = mid
}
}
return left
}
牛客 74 数字在升序数组中出现的次数
- 二分法找上下界,找元素右边的时候,找到的实际为大于元素的位置
package main
/**
*
* @param data int整型一维数组
* @param k int整型
* @return int整型
*/
func GetNumberOfK( data []int , k int ) int {
// write code here
left,right:=0,len(data)
//找左边
for left<right{
mid:=left+(right-left)/2
if data[mid]<k{
left = mid+1
}else{
right = mid
}
}
l:=left
left,right=0,len(data)
//找右边大于元素的位置
for left<right{
mid:=left+(right-left)/2
if data[mid]<=k{
left = mid+1
}else{
right = mid
}
}
r:=left
return r-l //不用+1,刚好是长度,因为r是大于元素的位置
}
34 排序数组中找到元素第一个和最后一个位置
- 两个二分,一个找左,一个找右
找右边之前需要特殊判断,
如果在左边就找不到元素,则
func searchRange(nums []int, target int) []int {
if len(nums)==0{
return []int{-1,-1}
}
left:=searchLeft(nums,target)
if left==len(nums)|| nums[left]!=target{ //没找到直接返回, 前面的left==len(nums)是因为,[2,2]找3,最后left是2,已经越界
return []int{-1,-1}
}
right:=searchRight(nums,target)-1
return []int{left,right}
}
//找重复元素左侧,没有找到的是大于元素的位置,因为left = mid+1
func searchLeft(nums[]int,target int)int{
if len(nums)==0{
return -1
}
left,right:=0,len(nums)
for left<right{
mid:=left+(right-left)/2
if nums[mid]<target{
left = mid+1
}else{ //包含等于的情况,nums[mid]等于目标值,但不一定是最左边的.
right = mid
}
}
return left //返回的下标不一定是目标值例如 [0,2,2,3] ,target=1,则第一次mid = (0+3)/2=1,最后left = righ = 1,最后返回的left是2的下标.
}
//有重复元素右侧大于元素的下标
func searchRight(nums[]int,target int)int{
if len(nums)==0{
return -1
}
left,right:=0,len(nums)
for left<right{
mid:=left+(right-left)/2
if nums[mid]<=target{ //等于,则左侧向右最后减一
left = mid+1
}else{
right = mid
}
}
return left
}
搜索旋转数组最小值 1,2
题目链接2 1在2题上面
- 二分去重,通用解法 o(logn)
需要用mid和右边判断,确定自己的位置
//比较nums[mid]和nums[right]大小
func findMin(nums []int) int {
//二分去重
left,right:=0,len(nums)-1
for left<right{
mid:=left+(right-left)/2
if nums[mid]>nums[right]{
left = mid+1
}else if nums[mid]<nums[right]{
right = mid
}else{ //相等right--, 无论1当时的mid在左边还是右边都是对的
right--
}
}
return nums[left]
}
33 搜索旋转排序数组1 (需要<=)
- 特殊情况,二分需要left<=right
func search(nums []int, target int) int {
//特殊题目使用=
left,right:=0,len(nums)-1
for left<=right{
mid := left+(right-left)/2
if nums[mid]==target{
return mid
}else if nums[mid]>=nums[left]{ //中点大于左边,则在左边寻找
if target>=nums[left]&&target<nums[mid]{ //在左边和中点之间,right-
right = mid-1
}else{
left = mid+1
}
}else{ //小于左边,则在右边寻找
if target>nums[mid]&&target<=nums[right]{
left = mid+1
}else{
right = mid-1
}
}
}
return -1
}
81 搜索旋转排序数组2(需要<=)
- 特殊判断,防止nums[mid]==nums[left]影响判断
题目链接
func search(nums []int, target int) bool {
//防止重复干扰计算 nums[left]==nums[mid]时候left++
left,right:=0,len(nums)-1
for left<=right{
mid:=left+(right-left)/2
if nums[mid]==target{
return true
}else if nums[mid]==nums[left]{
left++
}else if nums[mid]>nums[left]{ //左边寻找
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
}
}
}
return false
}
牛客91 最长递增子序列(具体序列)
可以参考算法导论
题目链接
dp会超时O(n^2)
二分求具体序列O(nlogn)
package main
import "math"
func LIS( arr []int ) []int {
//两个数组
n:=len(arr)
tail:=make([]int,n+1) //存储LIS长度为i的最小结尾数字,0不使用
tail[0] = math.MinInt32
dp:=make([]int,n) //存储arr[i]结尾的LIS长度
end:=0 //记录LIS结尾下标
for i:=0;i<n;i++{
num:=arr[i]
if num>tail[end]{
end++
tail[end] = num
dp[i] = end
}else{ //二分找到前面的大于元素的位置进行替换
left,right:=1,end
for left<right{
mid:=left+(right-left)/2
if tail[mid]<num{
left = mid+1
}else{
right = mid
}
}
//更新数据
tail[left] = num
dp[i] = left //i位置最长序列更新
}
}
//字典序最小,如果dp[i]=dp[j],i<j,一定arr[j]<arr[i] 否则dp[j]应该增加
res:=make([]int,end) //找到最长递增子序列
size:=end
for i:=n-1;i>=0;i--{
if dp[i]==size{
res[size-1] = arr[i]
size--
}
}
return res
}
leetcode 287寻找重复数
- 二分法,时间复杂度 O(nlogn) 空间O(1) 抽屉原理,计算抽屉和苹果数量是否相等
- //这个是计算哪个数字重复的,肯定是最初的重复数字影响了后续数字的计算,所以找符合条件的左侧,模板1(类似错误版本问题)
func findDuplicate(nums []int) int {
//二分逼近, 从1到n-1
l,r:=1,len(nums)-1
for l<r{
mid:=l+(r-l)/2
cnt:=0
for i:=0;i<len(nums);i++{
if nums[i]<=mid{
cnt++
}
}
//左边符合条件则向右,右边向左边逼近,
if cnt>mid{
r = mid
}else{
l = mid+1
}
}
return l
}