Golang算法模板-二分三分模板

61 阅读5分钟

二分查找模板

以下情况只考虑下标从0开始

参考链接https://leetcode.cn/leetbook/read/binary-search/xewjg7/

这里的模板2中,也可以写成right=length-1

image.png

模板1

最简单的二分搜索模板,不需要找第一个、最后一个位置,也没有重复元素,可以使用这个模板

注意1,跳出循环有两种情况

  • 直接mid就是答案,此时l和r无规律
  • 没有找到答案,此时一定right+1==left

注意2,由于循环退出条件是l<=r,也就是说当l==r时是不会退出循环的,因此不能出现l=mid,也不能出现r=mid

比如最后缩小到[1,1]区间时,此时l=1,r=1,mid=1,如果l=mid,或r=mid,则不会退出循环

func search(arr []int, value int) int {
    l, r := 0, len(arr)-1
    for l <= r {
        mid := l + (r-l)/2
        if arr[mid] == value {
            return mid
        } else if arr[mid] < value {
            l = mid + 1
        } else {
            r = mid - 1
        }
    }
    //r+1 == l
    return -1
}
func main() {
    var arr []int = []int{1, 2, 3, 4, 5}
    val := 3
    fmt.Println(search(arr, val))
}

浮点数模板

Pie

https://vjudge.net/problem/POJ-3122

func check(n, f int, cur float64, piArray []float64) bool {
    var sum = 0
    for i := 0; i < n; i++ {
        sum += int(math.Floor(piArray[i] / cur))
    }
    if sum >= f+1 {
        return true
    } else {
        return false
    }
}
func main() {
    var pi float64 = math.Pi
    var eps float64 = 1e-6
    var r int
    var n, f int
    fmt.Scan(&r)
    for {
        if r -= 1; r == -1 {
            break
        }
        fmt.Scan(&n, &f)
        piArray := make([]float64, n)
        left, right, mid := 0.0, 0.0, 0.0
        for i := 0; i < n; i++ {
            fmt.Scan(&piArray[i])
            piArray[i] = pi * piArray[i] * piArray[i]
            right = math.Max(right, piArray[i])
        }
        for math.Abs((right - left)) >= eps {
            mid = left + (right-left)/2
            if check(n, f, mid, piArray) {
                left = mid
            } else {
                right = mid
            }
        }
        fmt.Printf("%.4f\n", left)
    }
}

模板2

注意1,跳出循环时,一定有left==right

注意2,循环里不能同时出现left=midright=mid,只能出现其中一个

比如最后缩小到[1,2]区间时,此时l=1,r=2,mid=1

  • 如果mid是向下取整,mid=left+ (right-left)/2,则判断后不能出现left=mid,否则会死循环
  • 如果mid是向上取整,mid=left+(right-left+1)/2,则判断后不能出现right=mid,否则会死循环

模板3

搜索条件需要访问元素的直接左右两个邻居

使用元素的左右两个邻居来确定是向左还是向右

保证查找空间在每个步骤中至少有3个元素

需要进行后续处理,退出查找后,一定有left+1==right,这个区间有两个元素,因此这里是left=mid, right=mid

例题

35.搜索插入位置

https://leetcode.cn/problems/search-insert-position/description/

题目描述

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

//模板1
func searchInsert(nums []int, target int) int {
    left, right := 0, len(nums)-1
    for left <= right{
        mid := left + (right-left)/2
        if (target < nums[mid]){
            right = mid - 1
        }else if (target > nums[mid]){
            left = mid +1
        }else {
            return mid
        }
    }
    return left
}
//模板2
func searchInsert(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] < target{
            left = mid+1
        }else{
            right = mid
        }
    }
    if nums[left]>target{
        return left
    }else{
        return left+1
    }
}

163.寻找峰值

https://leetcode.cn/problems/find-peak-element/description/

题目描述

峰值元素是指其值严格大于左右相邻值的元素。

给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。

你可以假设 nums[-1] = nums[n] = -∞

你必须实现时间复杂度为 O(log n) 的算法来解决此问题。

//模板3
func findPeakElement(nums []int) int {
    left, right := 0, len(nums)-1
    for left+1 < right{
        mid := left + (right-left)/2
        if nums[mid-1] < nums[mid]{
            left = mid
        }else{
            right = mid
        }
    }
    if nums[left] > nums[right]{
        return left
    }else{
        return right
    }
}

搜索区间

https://www.lintcode.com/problem/search-for-a-range/description

题目描述

给定一个包含 n 个整数的排序数组,找出给定目标值 target 的起始和结束位置。

如果目标值不在数组中,则返回[-1, -1]

即给定一个数,查找其在数组中第一个和最后一个出现的下标

//模板3
func SearchRange(arr []int, target int) []int {
    if len(arr) == 0{
        return []int{-1, -1}
    }
    result := make([]int, 2)
    
    //第一次出现的下标
    left, right := 0, len(arr)-1
    for left+1 < right{
        mid := left + (right-left)/2
        if arr[mid] >= target{  //相等的话就往左找
            right=mid
        }else {
            left =mid
        }
    }
    if arr[left] == target{
        result[0] = left
    }else if arr[right] == target{
        result[0] = right
    }else{
        result[0], result[1] = -1, -1
        return result
    }
​
    //最后一次出现的下标
    left, right = 0, len(arr)-1
    for left + 1 < right{
        mid := left + (right-left)/2
        if arr[mid]<=target{//相等的话就往右找
            left = mid
        }else{
            right = mid
        }
    }
    if arr[right] == target{
        result[1] = right
    }else if arr[left] == target{
        result[1] = left
    }
    return result
}

牛牛分蛋糕

https://www.nowcoder.com/practice/435b7ac0d7eb4927a0ce4ef2ffcc1385

题目描述

牛牛今天家里要来客人,所以牛牛今天特意做了他最拿手的两种蛋糕,但是他是一个有洁癖的人,所以他在分蛋糕时,有如下几个原则:

1.他不希望一个盘子里出现两种蛋糕

2.他希望每个盘子中都有蛋糕

3.他想让装有最少蛋糕数量的盘子中装有的蛋糕数量尽可能多

//模板1
func check(n, a, b, cur int) bool {
    if a/cur+b/cur >= n {
        return true
    } else {
        return false
    }
}
func splitCake(n int, a int, b int) int {
    left, right := 1, int(math.Min(float64(a), float64(b)))
    var ans int
    for left <= right {
        mid := left + (right-left)/2
        if check(n, a, b, mid) {
            left = mid + 1
            ans = mid
        } else {
            right = mid - 1
        }
    }
    return ans
}
//模板2
func check(n, a, b, cur int) bool {
    if a/cur+b/cur >= n {
        return true
    } else {
        return false
    }
}
func splitCake(n int, a int, b int) int {
    left, right := 1, int(math.Min(float64(a), float64(b)))
    for left < right {
        mid := left + (right-left+1)/2  //注意这里要找可行域的有边界,所以这里需要向上取整,防止死循环
        if check(n, a, b, mid) {
            left = mid
        } else {
            right = mid - 1
        }
    }
    return left
}
//模板3
func check(n, a, b, cur int) bool {
    if a/cur+b/cur >= n {
        return true
    } else {
        return false
    }
}
func splitCake(n int, a int, b int) int {
    left, right := 1, int(math.Min(float64(a), float64(b)))
    for left+1 < right {
        mid := left + (right-left)/2
        if check(n, a, b, mid) {
            left = mid
        } else {
            right = mid
        }
    }
    if check(n, a, b, right) {
        return right
    } else {
        return left
    }
}

三分模板

二分查找适用于单调性(包括非严格单调)的函数或数组

如果一个数组序列或函数不单调,有一个拐点的话,需要使用三分查找

三分查找模板https://blog.csdn.net/weixin_42172261/article/details/88926415