文章目录
查找
导言
- 以下代码都存放于 我的GitHub仓库 ,如果小伙伴觉得有用,请给我颗星星哈。
- 以下代码都是提交过的,正确性可以保证。
二分查找
1. 简单二分查找
1. 寻找数组中「任意一个等于目标值的元素」值的索引
从非递减数组 nums
中找出任意一个索引 index
,使得 nums[index] == target
,index
不存在时返回 -1
。
func binarySearch(nums []int, target int) int {
l, r := 0, len(nums)-1
for l <= r {
mid := l + (r-l)/2
if nums[mid] == target {
return mid
} else {
if nums[mid] > target {
r = mid - 1
} else {
l = mid + 1
}
}
}
return -1
}
2. 进阶二分查找
1. 寻找数组中「第一个大于目标值的元素」的索引
从非递减数组 nums
中找出一个索引 index
,使得 nums[index] > target
,要求 index
尽可能的小,不存在时返回 len(nums)
。
// 例子: firstGreater([]int{1,2,3,4,5}, 4) -> return 4
// 「第一个大于」
func firstGreater(nums []int, target int) int {
l, r := 0, len(nums)-1
for l <= r {
mid := l + (r-l)/2
if nums[mid] == target {
l = mid + 1
} else {
if nums[mid] > target {
r = mid - 1
} else {
l = mid + 1
}
}
}
return l
}
2. 寻找数组中「第一个大于等于目标值的元素」的索引
从非递减数组 nums
中找出一个索引 index
,使得 nums[index] >= target
,要求 index
尽可能的小,不存在时返回 len(nums)
。
// 例子: firstGreaterOrEqual([]int{1,2,3,4,5}, 4) -> return 3
// 「第一个大于等于」
func firstGreaterOrEqual(nums []int, target int) int {
l, r := 0, len(nums)-1
for l <= r {
mid := l + (r-l)/2
if nums[mid] == target {
r = mid - 1
} else {
if nums[mid] > target {
r = mid - 1
} else {
l = mid + 1
}
}
}
return l
}
3. 寻找数组中「最后一个小于等于目标值的元素」的索引
从非递减数组 nums
中找出一个索引 index
,使得 nums[index] <= target
,要求 index
尽可能的大,不存在时返回 -1
。
// 例子: lastLessOrEqual([]int{1,2,3,4,5}, 4) -> return 3
// 「最后一个小于等于」
func lastLessOrEqual(nums []int, target int) int {
l, r := 0, len(nums)-1
for l <= r {
mid := l + (r-l)/2
if nums[mid] == target {
l = mid + 1
} else {
if nums[mid] > target {
r = mid - 1
} else {
l = mid + 1
}
}
}
return r
}
4. 寻找数组中「最后一个小于目标值的元素」的索引
从非递减数组 nums
中找出一个索引 index
,使得 nums[index] < target
,要求 index
尽可能的大,不存在时返回 -1
。
// 例子: lastLess([]int{1,2,3,4,5}, 4) -> return 2
// 「最后一个小于」
func lastLess(nums []int, target int) int {
l, r := 0, len(nums)-1
for l <= r {
mid := l + (r-l)/2
if nums[mid] == target {
r = mid - 1
} else {
if nums[mid] > target {
r = mid - 1
} else {
l = mid + 1
}
}
}
return r
}
3. 二分查找框架
func function(nums []int, target int) int {
l, r := 0, len(nums)-1
for l <= r {
mid := l + (r-l)/2
if nums[mid] == target {
/*
以下是「索引选择」,有3个选项,单选。
A. 找等于目标值的索引时: return mid
B. 找尽可能小的索引时: r = mid - 1
C. 找尽可能大的索引时: l = mid + 1
*/
} else {
if nums[mid] > target {
r = mid - 1
} else {
l = mid + 1
}
}
}
/*
以下是「返回值选择」,有3个选项,单选。
A. 找等于目标值的索引时: return -1
B. 找小于、小于等于target的索引时: return r
C. 找大于、大于等于target的索引时: return l
*/
}
4. 注意点
- 以上查找要求数组非递减,所以不适用于旋转排序数组。
- 解决旋转排序数组问题,需要用到二分查找的思想。
- 一般情况下,题目不会这么"裸",所以实际应用中,可能需要修改一些地方、或进行一些转换。
5. 练习题
- 33. 搜索旋转排序数组
- 34. 在排序数组中查找元素的第一个和最后一个位置
- 74. 搜索二维矩阵
- 81. 搜索旋转排序数组 II
- 153. 寻找旋转排序数组中的最小值
- 154. 寻找旋转排序数组中的最小值 II
- 240. 搜索二维矩阵 II
- 278. 第一个错误的版本
- 704. 二分查找
随机选择
1. 选择数组中的「第K小」与「第K大」。
// 选择第k小的数 (重复的数次序不等)
// 比如 [1 2 2 3],2是第2小、也是第3小。
func selectSmallKth(nums []int, k int) int {
l, r := 0, len(nums)-1
for l <= r {
index := randomPartition(nums, l, r)
if index+1 == k {
return nums[index]
} else {
if index+1 > k {
r = index - 1
} else {
l = index + 1
}
}
}
return -100000000 // 表示没找到 (k非法了)
}
// 选择第k大的数 (重复的数次序不等)
func selectBigKth(nums []int, k int) int {
// 第k大 == 第 len(nums)-k+1 小
return selectSmallKth(nums, len(nums)-k+1)
}
// 随机划分 (l,r在合法范围内)
// 作用: 经过划分后,使得 元素x左边 <= 元素x <= 元素x右边,返回元素x此时在数组中的索引。
func randomPartition(nums []int, l int, r int) int {
randomIndex := rand.Intn(r-l+1) + l // 选择随机索引
nums[randomIndex], nums[l] = nums[l], nums[randomIndex] // 打乱数组
guardIndex := l
for l <= r {
for l <= r && nums[l] <= nums[guardIndex] {
l++
}
for l <= r && nums[r] >= nums[guardIndex] {
r--
}
if l <= r {
nums[l], nums[r] = nums[r], nums[l]
}
}
nums[guardIndex], nums[r] = nums[r], nums[guardIndex]
return r
}
2. 拓展
- 如果要求数组中相同的数,它们的次序是一样的,那需要对数组进行怎样的预处理呢?
- 如果数组元素太多,以致内存无法装下,请问还有什么办法获得第K大吗?
(欢迎在评论区给我留言~)