搞懂二分法,看这一篇就够了

60 阅读2分钟

口诀

  1. 通用 left := 0,right := len(nums) - 1 ,for left <= right
  2. 找左边 等于不返回,收缩 right = mid - 1,返回 left
  3. 找右边 等于不返回,收缩 left = mid + 1, 返回 right
  4. 找值 等于返回mid

实战

1. 找值

func search(nums []int, target int) int {
	left := 0
	right := len(nums) - 1

	for left <= right {
		mid := (left + right) / 2
		if nums[mid] == target {
			return mid
		}
		if nums[mid] < target {
			left = mid + 1
		} else {
			right = mid - 1
		}
	}
    return -1
}

2. 找左

在排序数组中查找元素的第一个和最后一个位置

func findLeft(nums []int, target int) int {
	left := 0
	right := len(nums) - 1
	find := false
	for left <= right {
		mid := left + (right-left)/2
		if nums[mid] == target {
			right = mid - 1 // 收缩右边界
			find = true
		} else if nums[mid] < target {
			left = mid + 1
		} else if nums[mid] > target {
			right = mid - 1
		}
	}
	if find {
		return left
	}
	return -1
}

3. 找右

func findRight(nums []int, target int) int {
	left := 0
	right := len(nums) - 1
	find := false
	for left <= right {
		mid := left + (right-left)/2
		if nums[mid] == target {
			left = mid + 1 // 收缩左边界
			find = true
		} else if nums[mid] < target {
			left = mid + 1
		} else if nums[mid] > target {
			right = mid - 1
		}
	}
	if find {
		return right
	}
	return -1
}
  • 你要找的是 target 的最右位置

  • nums[mid] == target 时:

    • 说明 mid 这个位置是合法解,但可能右边还有更靠后的 target
    • 所以我们把 左边界往右移 (left = mid + 1) ,继续尝试。 最终循环结束时:
  • right 停在最后一个等于 target 的位置

  • left 停在 target 的右侧第一个不等于 target 的位置

示例解析

给定数组:

nums = [1, 3, 3, 4, 5]
target = 3

搜索过程

  1. 第一次循环

    • left = 0, right = 4
    • mid = 2
    • nums[mid] = 3 == target
    • 操作:left = mid + 1 = 3
    • 状态:left = 3, right = 4

  1. 第二次循环

    • left = 3, right = 4
    • mid = 3
    • nums[mid] = 4 > target
    • 操作:right = mid - 1 = 3
    • 状态:left = 3, right = 3

  1. 第三次循环

    • left = 3, right = 3
    • mid = 3
    • nums[mid] = 4 > target
    • 操作:right = mid - 1 = 2
    • 状态:left = 3, right = 2

  1. 循环结束

    • 此时 left > right,循环退出。
  • right = 2
  • 这就是 最后一个等于 target 的位置

为了避免找不到目标时误返回,可以在 nums[mid] == target 时设置一个 find = true
最终根据 find 判断是否返回 -1