做法:先利用二分搜索得到未经过旋转的排序数组起始元素在给定数组的索引,我们将这个索引称为分界点。分界点会划分出两个有序区间,我们分别对这两个区间进行二分搜索,从而得到题目的解。
问题来了,如何利用二分搜索得到分界点?
我们直到,原始数组是有序的,而题目给的是经过旋转后的数组,因此旋转点就是题目给定数组的首元素。因此,可以利用给定数组中必然存在一个左区间所有的元素均大于等于旋转点这一性质来判断分界点。
34.在排序数组中查找元素第一个和最后一个位置
做法:利用二分模板分别查找左边界和右边界即可。
方法:二分。
注意事项:1. 越界 2. 精度不够
学到的知识:
- (left + right)/2 = left + (right - left )/2
- 左移运算符>> 的优先级低于加减乘除的优先级
方法:二分查找
步骤如下:
- 循环,该循环将数组尾端所有等于nums[0]的元素拿出二分搜索范围。
- 寻找旋转点
- 判断旋转点是否是target
- 对左右区间二分,查找是否存在target
方法:二分。
二分的本质是二段性,而非单调性;
二分的二段行可以进一步划分,不仅仅只有满足01特性(满足/不满足)的二段性可以使用二分,满足1?特性也可以二分。
方法:
- 二分,本题并不保证下一行的第一个数字一定大于本行的最后一个数字
- 抽象BST。题目所给的矩阵可以看作是一个以右上角的节点为根节点的二叉树。
思路和方法:二分。
这道题目可以使用二分来解决,但我不知道怎么二分。
那我就说一下我的做法吧。
根据H指数的定义,实际上是找一个最大的x
,使得
i < n - x,citations[i] <= x
,i >= n - x,citations[i] >= x
。那么当
i = n - x
时,有citations[i] >= x
根据以上分析,我的解法如下:
n = citations.size()
;- 对
citations
从小到大排序- 从左到右遍历
citations
,返回第一个n - i
,使得citations[i] >= n - i
。
题目要求:
citations
已经按照 升序排列,要求设计并实现对数时间复杂度的算法解决此问题
思路和算法:利用数组的有序性进行二分。
假设存在一个分割点x
,其值为citations[x]
。分割点右边(包括分割点)的论文数量是n - x
,若n - mid
满足是H
指数,则必然有citations[x] >= n - x
。要使得H
指数尽可能大,那么mid
就必须尽可能地小。下面代码是左右边界的更新方式。
while(left < right){
int mid = left + (right - left) / 2;
if(citations[mid] >= n - mid) right = mid;
// 说明有n - mid篇文章的引用次数至少是n - mid次,n - mid符合要求
// 题目中要求H指数尽可能大,所以mid尽可能地小,因此令right = mid
else left = mid + 1;
}
这道题目的二分真的是为所未闻。
本题目实际上是最长上升子序列的应用。
若数组的最长上升子序列的长度大于3,返回true
。
方法:二分 + 贪心
我的做题历程:最开始想到了二分,但我想的二分是二分数组,后来觉得不合适。看了答案,二分的是子数组的最大和。
言归正传,下面是二分 + 贪心的思路。
思路
在整个整数范围内二分子数组的最大和mid
,判断数组中是否能够分割出小于等于m
个子数组,使每个子数组的和都小于mid
,若不存在,说明mid
太小,答案在右区间中,于是令左边界等于mid + 1
,若存在,说明答案在左区间中,于是令有边界等于mid
。这样一次又一次地缩小查找范围,直到左右边界重合。
如何判断数组中是否能够分割出小于等于m
个子数组,使每个子数组的和都小于mid
?这就要用到贪心算法了。
贪心算法的具体思路是这样的:遍历整个数组,若当前子数组再加上当前元素的和小于等于mid
,就把当前元素加入到当前子数组中,否则另起一个新的子数组。最后统计子数组的数目和m
的大小。
看到这道题目,一个直观的想法是:
- 把每个区间的左端点
left
及其相应的索引idx
合在一起形成对组{left,idx}
,并存储在容器V
中 - 把
v
中的元素按照左端点从小到大的顺序排列 - 根据题意查找合法的区间左端点
C++
中的STL
就提供了这样一种数据结构map
。map
有如下特点:
- 底层实现是红黑树
- 内部元素按照键值从小到大的顺序有序排列
- 提供了查找函数,如
find
,lower_bound
,upper_bound
,这三个函数的时间复杂度均为 O ( log n ) O(\log n) O(logn)。其中,find
用于查找map
中是否存在键值key
所对应的元素,若存在,返回其迭代器,否则返回尾后迭代器;lower_bound
返回第一个键值大于等于key
的元素的迭代器,upper_bound
返回第一个键值大于key
的元素的迭代器。
方法:二分 + 双指针,双指针方法用于快速判断房屋能够被供暖器全部覆盖,二分方法用于逼近能被供暖器覆盖的最短半径。
先考虑最简单的一种情况:已知供暖器的覆盖半径r
,如何快速判断房屋能够被供暖器全部覆盖?做法如下:
- 对房屋位置数组
houses
和供暖器位置数组heaters
进行从小到大的排序 - 定义指针
i
和指针j
分别指向houses
和heaters
- 判断
houses[i]
是否位于heaters[j]
的覆盖范围内,即判断houses[i] >= heaters[j] - radius && houses[i] <= heaters[j] + radius
,若结果为真,则递增i
,否则就递增j
。 - 重复上述步骤,直到结束对
houses
或heaters
的遍历
容易得到,供暖器的覆盖半径r
一定满足
r
>
r
m
i
n
r > r_{min}
r>rmin,其中
r
m
i
n
r_{min}
rmin是供暖器的最小覆盖半径。如果能够提供供暖器的覆盖半径的区间,并要求从该区间中找到供暖器的最短覆盖半径,很容易会想到二分算法。由于该题目可以提供供暖器的覆盖半径区间,因此可以使用二分算法进行解决。
思路:二分 + 前缀和
题目要求等概率地寻找所有满足要求的点,因此该题目的关键在于准确计算所有满足要求点的数量。
注:为防止越界,求解分界点时尽量使用left + (right - left) / 2
。此外,这种写法还适用于迭代器。