记录二分查找思想相关题目的解题。
0. 思路
二分查找 难就难在,arr[mid]跟谁比.
我们的目的是:当进行一次比较时,一定能够确定答案在mid的某一侧。
一般的比较原则有:
如果有目标值target,那么直接让arr[mid] 和 target 比较即可。
如果没有目标值target,一般可以考虑 端点 。
1. 题目描述 - 华为 - 二维数组的查找
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
解题思路
假设arr数组,val,tar如下图所示:
如果我们把二分值定在右上角或者左下角,就可以进行二分。这里以右上角为例,左下角可自行分析:
图片说明:
1)设初始值为右上角元素,arr[0][5] = val,目标tar = arr[3][1]
2)接下来进行二分操作:
3)如果val == target,直接返回
4)如果 tar > val, 说明target在更大的位置,val左边的元素显然都是 < val,间接 < tar,说明第 0 行都是无效的,所以val下移到arr[1][5]
5)如果 tar < val, 说明target在更小的位置,val下边的元素显然都是 > val,间接 > tar,说明第 5 列都是无效的,所以val左移到arr[0][4]
6)继续步骤2)
Complexity
- 时间复杂度:O(m+n) ,其中m为行数,n为列数,最坏情况下,需要遍历m+n次。
- 空间复杂度:O(1)
Code
1 | class Solution: |
2. 题目描述 - 剑指OFFER - 旋转数组的最小数
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
解题思路
这里我们把 0. 思路 中提到的 target 看作是右端点,因为此题的右端点是一个拐点,与mid对比,有助于寻找最小值。
mid = low + (high - low)/2
需要考虑三种情况:
(1)array[mid] > array[high]:
出现这种情况的array类似[3,4,5,6,0,1,2],此时最小数字一定在mid的右边。
low = mid + 1
(2)array[mid] == array[high]:
出现这种情况的array类似 [1,0,1,1,1] 或者[1,1,1,0,1],此时最小数字不好判断在mid左边,还是右边,这时只好一个一个试
high = high - 1
(3)array[mid] < array[high]:
出现这种情况的array类似[2,2,3,4,5,6,6],此时最小数字一定就是array[mid]或者在mid的左边。因为右边必然都是递增的。
high = mid
注意这里有个坑:如果待查询的范围最后只剩两个数,那么mid 一定会指向下标靠前的数字。比如 array = [4,6],array[low] = 4 ,array[mid] = 4 ,array[high] = 6 ;
如果high = mid - 1,就会产生错误, 因此high = mid
但情形(1)中low = mid + 1就不会错误
Complexity
- 时间复杂度:二分,所以为O(longN), 但是如果是[1, 1, 1, 1],会退化到O(n)
- 空间复杂度:没有开辟额外空间,为O(1)
Code
1 | class Solution { |
3. 题目描述 - 剑指OFFER - 数字在排序数组中出现的次数
题目抽象:给定一个非递减排序的数组nums和一个目标值target,查找target出现的次数。
解题思路
暴力方法的时间复杂度为O(n),并且没有用到升序条件。
可以用二分法。因为有序,所以目标值target如果有多个,肯定是连在一起。又已知我们可以在有序数组中查找任意一个值,因此我们可以先查找目标范围的下界和上界。
下界first定义为:如果存在目标值,则指向第一个目标值,否则,如果不存在,则指向-1。
上界last定义为:如果存在目标值,则指向大于目标值的第一个值,否则,如果不存在,则指向-1。
Complexity
- 时间复杂度:二分,所以为O(longN), 但是如果是[1, 1, 1, 1],会退化到O(n)
- 空间复杂度:没有开辟额外空间,为O(1)
Code
1 | # -*- coding:utf-8 -*- |
相关题目
[1 ]