喵,小喵爬天梯系列~美国大公司。
第三梯:二分查找
首先给出一个二分框架:
low,high
while(low <= high){
x = (low + high) / 2;
if(judge(a[x])){
low = x+1;
}
else{
high = x-1;
}
}
judge(x):是一个判断的条件。
1、Sqrt(x)
题意:
实现 int sqrt(int x) 函数,计算并返回 x 的平方根。
分析:
sqrt(x)大家影帝啊日常使用这个函数,现在我们怎么搞出来这个算法呢?稍微用一些数学公式即可,什么二分、牛顿等。给出x求出一个a。
使得x = a^2。变形得到x - a^2 = 0;现在给出x,便利a即可,范围是[0,a],这样的话,小喵就很惊喜,排序数组已经出来,现在就是二分,二分就来了,剩下的就是我们最熟悉的二分框架了。
Code:
class Solution {
public:
/**
* @param x: An integer
* @return: The sqrt of x
*/
int sqrt(int x) {//äşŒĺˆ†
// write your code here
long long a=0,b=x;
while(a < b){
long long m = (a+b)/2;
if(m*m == x) break;
else if(m*m > x) b = m-1; //第一个judge(x)
else a = m+1;
}
if(b*b > x) b-=1;
return b;
}
};
小喵总结:看到有序的时候,能否想到二分;当无序的时候,能否通过排序的情况下,二分降低时间复杂度。
2、Search Insert Position
题意:
给定一个排序数组和一个目标值,如果在数组中找到目标值则返回索引。如果没有,返回到它将会被按顺序插入的位置。
你可以假设在数组中无重复元素。
分析:
这就是二分的框架,没有任何转化。题目给定数组中无重复元素,如果有重复元素,让你找到最前和最后的位置,你只需要在返回的位置上,前后试探一下即可。
Code:
class Solution {
/**
* param A : an integer sorted array
* param target : an integer to be inserted
* return : an integer
*/
public:
int searchInsert(vector<int> &A, int target) {
// write your code here
if(A.size() == 0) return 0;
int low = 0,high = A.size();
while(low < high){
int mid = (low + high) / 2;
if(A[mid] == target) return mid;
else if(A[mid] < target) low = mid + 1;
else high = mid ;
}
return high;
}
};
小喵总结:号称能写对二分算法的程序员不到一半。其实我们我们确实很容易忘掉这个边界问题。
3、Search a 2D Matrix
题意:
写出一个高效的算法来搜索 m × n矩阵中的值。
这个矩阵具有以下特性:
每行中的整数从左到右是排序的。
每行的第一个数大于上一行的最后一个整数。
分析:
这就是一个排序的数组,强行分成一个二维的矩阵,很明显遍历一遍,得到结果。O(mn)。既然整体有序,我们可以对[1-mn]进行二分,时间O(log(mn)) = O(log(m)+log(n))。我们唯一需要做的就是mid = (low+high)/2,将mid转化到二维数组的位置,(这是你是不是想起大学学的数据结构是,多维数组的转化还是有一定用处的,)m x n。
row = mid / m; column = mid % n。搞定,下面就是写出一个正确的二分算法就好了。
Code:
class Solution {
public:
/**
* @param matrix, a list of lists of integers
* @param target, an integer
* @return a boolean, indicate whether matrix contains target
*/
/** 此处是直接遍历的代码
bool searchMatrix(vector<vector<int> > &matrix, int target) {
// write your code here
if(matrix.size() == 0 ) return false;
int c = matrix.size(); //čĄŒ
int r = matrix[0].size(); //ĺˆ—
int k;
for(k = 0;k < c && target > matrix[k][r - 1];k ++);
if(k == c) return false;
for(int i = 0; i < r; i ++){
if(matrix[k][i] == target) return true;
}
return false;
}
**/
bool searchMatrix(vector<vector<int> > &matrix, int target){
if(matrix.size() == 0) return false;
int m = matrix.size(), n= matrix[0].size();
int mid,low = 0, high = m*n-1;
while(low <= high){//二分这个思想,可以很简单,但是边界一定要注意
mid = low + (high - low)/2;
if(target == matrix[mid/n][mid%n]){
return true;
}
else if(target < matrix[mid/n][mid%n]){
high = mid - 1;
}
else low = mid + 1;
}
return false;
}
};
4、First Position of Target
题意:
给定一个排序的整数数组(升序)和一个要查找的整数target,用O(logn)的时间查找到target第一次出现的下标(从0开始),如果target不存在于数组中,返回-1。
分析:
这题就是我上面提到的,如果要求返回的是第一个和最后一个这个值的下标呢?此时就用到了我说的前后试探法。最坏情况下O(n/2)。
Code:
class Solution {
public:
/**
* @param nums: The integer array.
* @param target: Target number to find.
* @return: The first position of target. Position starts from 0.
*/
int binarySearch(vector<int> &A, int target) {
// write your code here
if(A.size() == 0) return -1;
int mid, low = 0,high = A.size() - 1;
while(low <= high){
mid = (low + high) / 2;
if(A[mid] == target) break;
else if(A[mid] < target) low = mid + 1;
else high = mid-1 ;
}
if(low > high) return -1;
while(mid > 0 && A[mid - 1] == target){
mid --;
}
return mid;
}
};
5、Find Minimum in Rotated Sorted Array
题意:
假设一个旋转排序的数组其起始位置是未知的(比如0 1 2 4 5 6 7 可能变成是4 5 6 7 0 1 2)。
你需要找到其中最小的元素。
分析:
这里我推荐先看代码,再看解释,既然是二分的章节,那么问题是怎么二分呢?首先我们要知道旋转后的数组的性质,旋转之后,我们得到两个连续的有序数组,假设原数组A,旋转之后得到两个有序数组ab,mid = (low+high)/2;每一次判断A[mid] > A[high] 最小值一定在右边部分, 我们可以看到high位置一定是b部分的最大值,mid位置可能是a部分的任意值或者是b部分的值,连续这个性质,我们可以得到这个结果。(我知道我没有说清楚,各位别打我)。反之,最小值在左边。
Code:
class Solution {
public:
/**
* @param num: a rotated sorted array
* @return: the minimum number in the array
*/
int findMin(vector<int> &num) {
// write your code here
int high=num.size()-1;
int low=0,mid;
while(low<high){
mid=(low+high)/2;
if(num[mid]>num[high]) low=mid+1;
else high=mid;
}
return num[low];
}
};
6、Find Peak Element
题意:
你给出一个整数数组(size为n),其具有以下特点:
相邻位置的数字是不同的
A[0] < A[1] 并且 A[n - 2] > A[n - 1]
假定P是峰值的位置则满足A[P] > A[P-1]且A[P] > A[P+1],返回数组中任意一个峰值的位置。
分析:
我们可以直接套用二分的框架,只是在判断是时候,比较的a[mid]和a[mid-1]或者a[mid]和a[mid+1]。对于这用一条语句的判断,我们一般不写成一个judge(x)的函数,但是这个函数的思想是必要的,因为这是二分搜索时,最需要关注的地方,之后才是二分的边界问题。
Code:
class Solution {
public:
/**
* @param A: An integers array.
* @return: return any of peek positions.
*/
int findPeak(vector<int> A) {
// write your code here
int start, end, mid;
start = 1;
end = A.size() - 2;
while(start + 1 < end){
mid = (start + end) / 2;
if(A[mid] < A[mid - 1]){
end = mid;
}
else{
start = mid;
}
}
if(A[start] < A[end]) return end;
else return start;
}
};
7、First Bad Version
题意:
代码库的版本号是从 1 到 n 的整数。某一天,有人提交了错误版本的代码,因此造成自身及之后版本的代码在单元测试中均出错。请找出第一个错误的版本号。
你可以通过 isBadVersion 的接口来判断版本号 version 是否在单元测试中出错,具体接口详情和调用方法请见代码的注释部分。
分析:
isBadVersion就是我们框架中的judge函数,只需要替换一下就可以了。
Code:
/**
* class SVNRepo {
* public:
* static bool isBadVersion(int k);
* }
* you can use SVNRepo::isBadVersion(k) to judge whether
* the kth code version is bad or not.
*/
class Solution {
public:
/**
* @param n: An integers.
* @return: An integer which is the first bad version.
*/
int findFirstBadVersion(int n) {
// write your code here
int low=1,high=n,mid;
while(low<=high){
mid = (low+high)/2;
if(SVNRepo::isBadVersion(mid)) high = mid-1;
else low = mid+1;
}
return low;
}
};
8、Search in Rotated Sorted Array
题意:
假设有一个排序的按未知的旋转轴旋转的数组(比如,0 1 2 4 5 6 7 可能成为4 5 6 7 0 1 2)。给定一个目标值进行搜索,如果在数组中找到目标值返回数组中的索引位置,否则返回-1。
分析:
这次可以第五题的框架,只是在条件的内部,需要再加上一个判断,确定这个target是否在当前的部分之中。
Code:
class Solution {
/**
* param A : an integer ratated sorted array
* param target : an integer to be searched
* return : an integer
*/
public:
int search(vector<int> &A, int target) {
// write your code here
if(A.size() == 0) return -1;
int f=0,e=A.size()-1,m;
while(f<=e){
m = (f + e) / 2;
if(A[m] == target) return m;
if(A[f] <= A[m]){
if(target >= A[f] && target <= A[m]) e = m - 1;
else f = m + 1;
}
else{
if(target <= A[e] && A[m] <= target) f = m + 1;
else e = m - 1;
}
}
return -1;
}
};
9、Search for a Range
题意:
给定一个包含 n 个整数的排序数组,找出给定目标值 target 的起始和结束位置。
如果目标值不在数组中,则返回[-1, -1]
分析:
二分框架+左右试探。还可以使用C++中的库函数。
Code:
class Solution {
/**
*@param A : an integer sorted array
*@param target : an integer to be inserted
*return : a list of length 2, [index1, index2]
*/
public:
/**
//使用C++库函数
vector<int> searchRange(vector<int> &A, int target) {
// write your code here
vector<int> res;
vector<int>::iterator f,e;
f = lower_bound(A.begin(),A.end(),target);
e = upper_bound(A.begin(),A.end(),target);
if(f==A.end()||*f!=target){
res.push_back(-1); res.push_back(-1);
}
else{
res.push_back(f-A.begin()); res.push_back(e-A.begin()-1);
}
return res;
}
**/
//二分+左右试探,注意边界
vector<int> searchRange(vector<int> &A, int target){
int mid, low = 0,high = A.size() - 1;
vector<int> res(2,-1);
if(high == -1) return res;
while(low <= high){
mid = (low + high) / 2;
if(A[mid] == target) break;
else if(A[mid] < target) low = mid + 1;
else high = mid-1 ;
}
if(A[mid] != target) return res;
low = high = mid;
while(low > 0 && A[low - 1] == target){
low --;
}
while(high < A.size() - 1 && A[high + 1] == target){
high ++;
}
res[0] = low;
res[1] = high;
return res;
}
};
10、Wood Cut
题意:
有一些原木,现在想把这些木头切割成一些长度相同的小段木头,需要得到的小段的数目至少为 k。当然,我们希望得到的小段越长越好,你需要计算能够得到的小段木头的最大长度。
分析:
我们需要的最大长度L,最大长度L是多少?肯定在[0,maxlength]之间的一个数。此时我们注意到了一个排序的数组,能否二分?judge(x)怎么计算?注意到题目要求至少是k个小段,judge(x)只需要能否满足k个即可,如果得到的小木块大于k,可以得到mid = low + 1;否则high = mid - 1。
Code:
class Solution {
public:
/**
*@param L: Given n pieces of wood with length L[i]
*@param k: An integer
*return: The maximum length of the small pieces.
*/
int judgeLength(vector<int> L, int k, int length){
if(length == 0) return -1;
int cnt = 0;
for(int i = 0; i < L.size(); i ++){
cnt += L[i] / length;
}
if(cnt < k) return 0;
if(cnt >= k) return 1; //长度短
}
int woodCut(vector<int> L, int k) {
// write your code here
long long maxlen = 0, minlen = 0, midlen = 0;
for(int i = 0; i < L.size(); i ++){
if(maxlen < L[i]) maxlen = L[i];
}
while(minlen <= maxlen){
midlen = (maxlen + minlen) / 2;
if(judgeLength(L, k, midlen) == 1){
minlen = midlen + 1;
}
else if(judgeLength(L, k, midlen) == 0){
maxlen = midlen - 1;
}
else{
break;
}
}
return maxlen;
}
};