LintCode天梯(USGiants)-Binary Search

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

第三梯:二分查找

首先给出一个二分框架:

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;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值