⼆分搜索
思想
二分搜索分为:基本二分搜索,最左边的二分搜索,最右边的二分搜索。
防止溢出:计算 mid 时需要防⽌溢出,代码中 left + (right - left) / 2 就和 (left +
right) / 2 的结果相同,但是有效防⽌了 left 和 right 太⼤,直接相加导致溢出的情况。1.基本的二分搜索(左闭右闭的情况)
//基本的二分搜索 func binarySearch(nums []int,target int)int{ left := 0 right := len(nums) - 1 for left <= right{ mid := left + (right - left) / 2 if nums[mid] == target{ return mid }else if nums[mid] > target{ right = mid -1 }else if nums[mid] <target{ left = mid +1 } } return -1 }
2.基本的二分搜索(左闭右开)
func binarySearch1(nums []int,target int)int{ // 左闭右开 left := 0 // left表示可取 right := len(nums) // right表示末尾,且不能取 for left < right{ mid := left + (right - left) / 2 if nums[mid] == target{ return mid }else if nums[mid] > target{ // right表示不可取 right = mid }else if nums[mid] <target{ left = mid +1 } } return -1 }
3.左侧搜索
func left_bound(nums []int,target int) int{ left := 0 right := len(nums) -1 for left <= right{ mid := left + (right - left) / 2 if nums[mid] > target{ right = mid -1 }else if nums[mid] < target{ left = mid + 1 }else if target == nums[mid]{ right = mid - 1 // 等于的话必须左移动 } } if left >= len(nums) || nums[left] != target{ return -1 } return left }
4.右侧搜索
func right_bound(nums []int,target int) int{ left := 0 right := len(nums) -1 // right表示末尾,不可取 for left <= right{ mid := left + (right - left) / 2 if nums[mid] > target{ right = mid -1 }else if nums[mid] < target{ left = mid + 1 }else if nums[mid] == target{ left = mid + 1 } } if right < 0 || nums[right] != target{ return -1 } return right }
例题
34. 在排序数组中查找元素的第⼀个和最后⼀个位置
func searchRange(nums []int, target int) []int { res := []int{left_bound(nums,target),right_bound(nums,target)} return res } func left_bound(nums []int,target int) int{ left := 0 right := len(nums) -1 for left <= right{ mid := left + (right - left) / 2 if nums[mid] > target{ right = mid -1 }else if nums[mid] < target{ left = mid + 1 }else if target == nums[mid]{ right = mid - 1 // 等于的话必须左移动 } } if left >= len(nums) || nums[left] != target{ return -1 } return left } func right_bound(nums []int,target int) int{ left := 0 right := len(nums) -1 // right表示末尾,不可取 for left <= right{ mid := left + (right - left) / 2 if nums[mid] > target{ right = mid -1 }else if nums[mid] < target{ left = mid + 1 }else if nums[mid] == target{ left = mid + 1 } } if right < 0 || nums[right] != target{ return -1 } return right }
35. 搜索插⼊位置
func searchInsert(nums []int, target int) int { left , right := binarySearch(nums,target) if left >= len(nums) { return len(nums) } if right < 0{ return 0 } return left } func binarySearch(nums []int , target int)(int,int){ left := 0 right := len(nums) - 1 for left <= right{ mid := left + (right - left) / 2 if nums[mid] > target{ right = mid - 1 }else if nums[mid] < target{ left = mid + 1 }else if nums[mid] == target{ return mid , 0 } } return left , right }
354. 俄罗斯套娃信封问题
// 时间超时-先排序在按照dp去做
func maxEnvelopes(envelopes [][]int) int { sort.Slice(envelopes, func(i, j int) bool { if envelopes[i][0] > envelopes[j][0]{ return false }else if envelopes[i][0] < envelopes[j][0]{ return true }else { if envelopes[i][1] > envelopes[j][1]{ return false }else if envelopes[i][1] <= envelopes[j][1] { return true } } return false }) //for _,i := range envelopes{ // fmt.Println() // for _,j := range i{ // fmt.Print(j," ") // } //} max := 0 clen := len(envelopes) dp := make([]int,clen) for i:=0;i<clen;i++{ dp[i] = 1 } for i:=0;i<clen;i++{ cv := 1 for j := i - 1 ;j>=0;j--{ if envelopes[i][0] > envelopes[j][0] && envelopes[i][1] > envelopes[j][1]{ // dp[i] = dp[j] + 1 // break if cv < dp[j] + 1{ cv = dp[j] + 1 } } } dp[i] = cv if dp[i] > max{ max = dp[i] } } return max }
392. 判断⼦序列
func isSubsequence(s string, t string) bool { if len(s) == 0{ return true } index := 0 for i :=0; i<len(t);i++ { if t[i] == s[index]{ index ++ } if index == len(s){ return true } } return false }
704. ⼆分查找
func search(nums []int, target int) int { left := 0 right := len(nums) - 1 for left <= right{ mid := left + (right - left) / 2 if nums[mid] > target{ right = mid - 1 }else if nums[mid] < target{ left = mid + 1 }else if nums[mid] == target{ return mid } } return -1 }
793. 阶乘函数后 K 个零
// 思想是考虑0的得出,计算2和5的个数,因为5的个数比2少,所以计算5的个数就能得出结果,又因为从0到无从大,末尾零的个数是线性增加的,取得了两边的边界就能得出结果,所以采用二分去做。
func preimageSizeFZF(k int) int { return rightBound(0,math.MaxInt64,k) - leftBound(0,math.MaxInt64,k) + 1 } func leftBound(left int ,right int, target int)int{ for left <= right{ mid := left + (right - left) / 2 if f(mid) > target{ right = mid - 1 }else if f(mid) < target{ left = mid + 1 }else if f(mid) == target{ right = mid - 1 } } return left } func rightBound(left int ,right int, target int)int{ for left <= right{ mid := left + (right - left) / 2 if f(mid) > target{ right = mid - 1 }else if f(mid) < target{ left = mid + 1 }else if f(mid) == target{ left = mid + 1 } } return right } func f(n int)int{ if n ==0{ return 0 } return n / 5 + f(n/5) }
875. 爱吃⾹蕉的珂珂(从答案的角度去进行二分查找-二分查找答案)
func minEatingSpeed(piles []int, h int) int { left := 1 right := 1000000001 for left < right{ mid := left + (right - left ) / 2 flag := speedToHour(piles,mid) if flag <= h{ right = mid }else if flag > h{ left = mid + 1 } } return left } func speedToHour(piles []int, speed int) (int) { res := 0 for _,val := range piles{ res += (val / speed) if val % speed != 0{ res ++ } } return res }
1011. 在 D 天内送达包裹的能⼒
func shipWithinDays(weights []int, days int) int { left := 1 for _,val := range weights{ if val > left{ left = val } } right := 500 * 50000 for left < right{ mid := left + (right - left) / 2 flag := transportDay(weights,mid) if flag > days{ left = mid + 1 }else if flag <= days{ right = mid } } return left } func transportDay(weights []int,transport int)int{ day := 0 cv := 0 for _,val := range weights{ if cv + val > transport{ if cv !=0{ day ++ } cv = val }else if cv + val == transport{ cv = 0 day ++ }else if cv + val < transport{ cv += val } } if cv > 0{ day ++ } return day }