美文网首页
PTA刷题总结-Part3.2 二分法专题

PTA刷题总结-Part3.2 二分法专题

作者: 苏wisdom | 来源:发表于2020-04-05 18:23 被阅读0次

    1 基本的二分查找

    01-复杂度3 二分查找 (20分)
    最开始使用的是递归,发现最后一个测试点超时了

    #include <stdio.h>
    #include <stdlib.h>
    
    #define MAXSIZE 10
    #define NotFound 0
    typedef int ElementType;
    
    typedef int Position;
    typedef struct LNode *List;
    struct LNode {
        ElementType Data[MAXSIZE];
        Position Last; /* 保存线性表中最后一个元素的位置 */
    };
    
    List ReadInput(); /* 裁判实现,细节不表。元素从下标1开始存储 */
    Position BinarySearch( List L, ElementType X );
    
    int main()
    {
        List L;
        ElementType X;
        Position P;
    
        L = ReadInput();
        scanf("%d", &X);
        P = BinarySearch( L, X );
        printf("%d\n", P);
    
        return 0;
    }
    
    /* 你的代码将被嵌在这里 */
    
    
    Position search(ElementType Data[], Position l, Position r,ElementType X){
        if (l==r){
            return Data[l]==X? l:NotFound; 
        }
        Position mid = (l+r)>>1;
        if (Data[mid] == X){
            return mid; 
        } else if (Data[mid] < X){
            return search(Data, mid+1, r,X); 
        } else {
            return search(Data, l, mid-1,X);
        }
    }
    
    Position BinarySearch( List L, ElementType X ){
        Position p = search(L->Data, 1, L->Last,X); 
        return p;
    }
    

    于是改成非递归的形式

    Position BinarySearch( List L, ElementType X ){
        Position p = 0;
        Position l =1,r = L->Last;
        while (l<=r){
            Position mid = l +((r-l)>>1);
            if (L->Data[mid] == X){
                p = mid;
                break; 
            }
            else if (L->Data[mid] < X){
                l = mid +1;
            } 
            else {
                r = mid -1;
            }
        }
        if (p==0){
            p = NotFound;
        }
        return p;
    }
    

    注意:

    • 上面写的是简单的二分查找,要求数据顺序存储在数组中(如果是在链表中,查找的时候就不是O(1)复杂度了),而且没有重复数据
    • 循环退出条件是l<=r,不是l<r
    • 取mid可以写为mid = l +((r-l)>>1);避免溢出,注意不要掉了两对括号,根据优先级(先算术运算,后移位运算,最后位运算),加法优先于右移。
    • left 和 right 更新时不应该使用mid,而应该使用mid-1或者mid+1。因为当数组中没有指定元素时,我们的判断条件while(l<=r)会导致程序陷入无限循环,相等才退出
    • 数据量太小不适合二分查找,比如只有10个数据元素,循环就好了
    • 数据量太大,比如1GB,由于二分查找需要连续的内存空间,所以也不适合

    题外话:基于链表的二分查找其实是有的。Redis中的有序集合(sorted set)使用的“跳表(Skip List)”数据结构,就是一种基于链表的查找方法,查询效率O(logn), 构建多级索引。

    2 二分查找拓展

    1. 查找第一个值等于给定值的元素
    2. 查找最后一个值等于给定值的元素
    3. 查找第一个大于等于给定值的元素
    4. 查找最后一个小于等于给定值的元素
    5. 求一个数的算术平方根,精确到小数点后6位
    6. 木棒切割问题

    1. 查找第一个值等于给定值的元素
    如在数组{1,3,4,5,6,8,8,8,11,18}中希望查找第一个等于8的下标位置(下标从0开始),输入数据格式:

    10
    1 3 4 5 6 8 8 8 11 18
    8
    

    运行程序后得到的结果是5

    public class Solution {
        /**
         * @param nums: The integer array.
         * @param target: Target to find.
         * @return: The first position of target. Position starts from 0.
         */
        public int binarySearch(int[] nums, int target) {
            if (nums == null || nums.length == 0) {
                return -1;
            }
            int start = 0;
            int end = nums.length - 1;
            while (start + 1 < end) {
                int mid = start + (end - start) / 2;
                if (nums[mid] == target) {
                    end = mid;
                } else if (target < nums[mid]) {
                    end = mid;
                } else {
                    start = mid;
                }
            }
            
            if (nums[start] == target) {
                return start;
            }
            if (nums[end] == target) {
                return end;
            }
            
            return -1;
        }
    }
    
    #include <stdio.h>
    int a[100];
    
    int search(int a[], int size, int X){
        int left = 0, right = size-1;
        while(left<=right){
            int mid = left + ((right - left) >> 1);
            if (X > a[mid]){
                left = mid + 1;
            }
            else if (X < a[mid]){
                right = mid -1;
            }
            else{ // a[mid] == X
                if (mid==0 || a[mid-1] < a[mid]){
                    return mid;
                }
                else {
                    right = mid - 1;
                }
            }
        }
        return -1;
    }
    int main(){
        int n = 0,x=0;
        scanf("%d", &n);
        for (int i=0;i<n;i++){
            scanf("%d", &a[i]);
        }
        scanf("%d", &x);
        int pos = search(a,n,x);
        printf("%d", pos);
    }
    
    

    2. 查找最后一个值等于给定值的元素
    如在数组{1,3,4,5,6,8,8,8,11,18}中希望查找最后一个等于8的下标位置(下标从0开始),输入数据格式:

    10
    1 3 4 5 6 8 8 8 11 18
    8
    

    运行程序后得到的结果是7

    public class Solution {
        /**
         * @param nums: An integer array sorted in ascending order
         * @param target: An integer
         * @return: An integer
         */
        public int lastPosition(int[] nums, int target) {
            if (nums == null || nums.length == 0) {
                return -1;
            }
            
            int start = 0;
            int end = nums.length - 1;
            
            while (start + 1 < end) {
                int mid = start + (end - start) / 2;
                if (nums[mid] == target) {
                    start = mid;
                } else if (target < nums[mid]) {
                    end = mid;
                } else {
                    start = mid;
                }
            }
            
            if (nums[end] == target) {
                return end;
            }
            if (nums[start] == target) {
                return start;
            }
            
            return -1;
        }
    }
    
    #include <stdio.h>
    int a[100];
    
    int search(int a[], int size, int X){
        int left = 0, right = size-1;
        while(left<=right){
            int mid = left + ((right - left) >> 1);
            if (X > a[mid]){
                left = mid + 1;
            }
            else if (X < a[mid]){
                right = mid -1;
            }
            else{ // a[mid] == X
                if (mid==size-1 || a[mid+1] > a[mid]){
                    return mid;
                }
                else {
                    left = mid + 1;
                }
            }
        }
        return -1;
    }
    int main(){
        int n = 0,x=0;
        scanf("%d", &n);
        for (int i=0;i<n;i++){
            scanf("%d", &a[i]);
        }
        scanf("%d", &x);
        int pos = search(a,n,x);
        printf("%d", pos);
    }
    

    3. 查找第一个大于等于给定值的元素
    如在数组{3,4,6,7,10}中希望查找第一个大于等于5的下标位置(下标从0开始),输入数据格式:

    5
    3 4 6 7 10
    5
    

    运行程序后得到的结果是2

    #include <stdio.h>
    int a[100];
    
    int search(int a[], int size, int X){
        int left = 0, right = size-1;
        while(left<=right){
            int mid = left + ((right - left) >> 1);
            if (a[mid]>=X){
                if (mid == 0 || a[mid-1] < X){
                    return mid;
                }
                else {
                    right = mid - 1;
                }
            }
            else {
                left = mid +1;
            }
        }
        return -1;
    }
    int main(){
        int n = 0,x=0;
        scanf("%d", &n);
        for (int i=0;i<n;i++){
            scanf("%d", &a[i]);
        }
        scanf("%d", &x);
        int pos = search(a,n,x);
        printf("%d", pos);
    }
    

    4. 查找最后一个小于等于给定值的元素
    如在数组{3,4,6,7,10}中希望查找最后一个小于等于5的下标位置(下标从0开始),输入数据格式:

    5
    3 4 6 7 10
    5
    

    运行程序后得到的结果是1

    #include <stdio.h>
    int a[100];
    
    int search(int a[], int size, int X){
        int left = 0, right = size-1;
        while(left<=right){
            int mid = left + ((right - left) >> 1);
            if (a[mid] <= X){
                if (mid == size-1 || a[mid+1] > X){
                    return mid;
                }
                else {
                    left = mid +1;
                }
            }
            else {
                right = mid - 1;
            }
        }
        return -1;
    }
    int main(){
        int n = 0,x=0;
        scanf("%d", &n);
        for (int i=0;i<n;i++){
            scanf("%d", &a[i]);
        }
        scanf("%d", &x);
        int pos = search(a,n,x);
        printf("%d", pos);
    }
    
    

    5. 求一个数的算术平方根,精确到小数点后6位

    #include <stdio.h>
    const double eps=1e-7;
    
    double f(double x){
        return x*x;
    }
    
    double mysqrt(double n){
        double left = 0;
        double right = n;
        double mid = -1;
        while (right - left > eps){
            mid = (left + right)/2;
            if (f(mid) > n){
                right = mid;
            }
            else {
                left = mid;
            }
        }
        return mid;
    }
    int main(){
        int n = 0;
        scanf("%d", &n);
        double root = mysqrt(n);
        printf("%lf", root);
    }
    
    

    6. 木棒切割问题

    给出N个木棒,长度已知。现在希望通过切割它们得到至少K个长度相等的木棒(长长度是整数)。问这些长度相等的木棒最长有多长。
    例如对于3根木棒,长度分别为10,24,15。想要得到至少7个长度相等的木棒,那么切割的木棒最长为6。

    有N条绳子,它们的长度分别为Li。如果从它们中切割出K条长度相同的绳子,这K条绳子每条最长能有多长?

    #include <stdio.h>
    int a[10000];
    
    int getCurrK(int len, int n){
        int currK = 0;
        for (int i=0;i<n;i++){
            currK += a[i]/len;
        }
        return currK;
    }
    
    int main(){
    
        int n=0,k=0,left=1,right=0,len = 0,sum=0;
        scanf("%d %d", &n, &k);
        for (int i=0;i<n;i++){
            scanf("%d", &a[i]);
            sum += a[i];
        }
        right = sum / k;
        while (left <= right){
            int mid = left + ((right - left)>>1);
            int currK = getCurrK(mid, n);
            if (currK < k){
                right = mid - 1;
            } else if (currK > k){
                left = mid + 1;
            } else {
                if (mid == sum / k || getCurrK(mid+1, n)<k ){
                    len = mid;
                    break;
                } else {
                    left = mid + 1;
                }
    
            }
        }
        printf("%d", len);
        return 0;
    }
    
    

    3 最大子列和问题

    最大子列和问题的三种解法

    #include <stdio.h>
    // O(n^3)
    int maxSubSum(int a[], int size,int max){
        
        for (int i=0;i<size;i++){
            for (int j=i;j<size;j++){
                int r = 0;
                for (int k=i;k<=j;k++){
                    r+=a[k];
                }
                if (r > max){
                    max = r;
                }
            }
        }
        return max;
    }
    // O(n^2)
    int maxSubSum2(int a[], int size,int max){
        
        for (int i=0;i<size;i++){
            int r = 0;
            for (int j=i;j<size;j++){
                r+=a[j];
                if (r > max){
                    max = r;
                }
            }
        }
        return max;
    }
    
    
    int conquer(int a[], int left,int mid,int right,int lr,int rr){
        int maxl = 0;
        int r = 0;
        for(int i=mid;i>=left;i--){
            r+=a[i];
            if (r>maxl){
                maxl = r;
            }
        }
        int maxr = 0;
        r = 0;
        for (int i=mid+1;i<=right;i++){
            r+=a[i];
            if (r>maxr){
                maxr = r;
            }
        }
        int maxlr = maxl + maxr;
        if (maxlr >= lr && maxlr >= rr){
            return maxlr;
        } else if (lr > maxlr && lr > rr){
            return lr;
        } else if (rr > maxlr && rr > lr){
            return rr;
        }
    }
    // O(NLogN) 二分法
    int maxSubSum3(int a[], int left,int right){
        if (left == right){
            return (a[left]>0)?a[left]:0;
        }
        else if (left < right){
            int mid = (left + right) >> 1;
            int lr = maxSubSum3(a,left,mid); // 获取左边最大
            int rr = maxSubSum3(a,mid+1,right); // 获取右边最大
            int max = conquer(a,left,mid,right,lr,rr); // 获取中间向两边扩展的最大值,并和左边、右边最大比较
            return max;
        }
    }
    int main(){
        int a[8] = {4,-3,5,-2,-1,2,6,-2};
        int max = 0;
        max = maxSubSum3(a,0,7);
        printf("%d", max);
        return(0);
    }
    

    当然,最大子列和还有“在线处理”这种耗时O(N)的算法
    01-复杂度2 Maximum Subsequence Sum (25分)

    #include <stdio.h>
    int a[100000];
    int main(){
        int n=0; // 读入n个数
        int max=0, thisSum = 0; // max=最终结果,thisSum=当前和
        scanf("%d", &n);
        for (int i=0; i<n; i++){
            scanf("%d", &a[i]);
            thisSum += a[i];
            if (thisSum < 0 ){ // 不可能使得后面的值增大了,抛弃
                thisSum = 0;
            }
            if (thisSum >max){ // 更新当前结果
                max = thisSum;
            }
        }
        printf("%d", max);
        return 0;
    }
    
    

    相关文章

      网友评论

          本文标题:PTA刷题总结-Part3.2 二分法专题

          本文链接:https://www.haomeiwen.com/subject/iqcpphtx.html