LintCode天梯(USGiants)-Binary Search

来源:互联网 发布:淘宝1.74非球面镜片 编辑:程序博客网 时间:2024/05/28 04:53

喵,小喵爬天梯系列~美国大公司。

第三梯:二分查找

首先给出一个二分框架:

low,highwhile(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;    }};