Split Array Largest Sum 二分查找应用 C实现

来源:互联网 发布:mysql access 对比 编辑:程序博客网 时间:2024/06/01 17:15
  • 题目描述
    这里写图片描述

  • 初步分析
    该题乍一看没有什么思路,于是我便直接思考用二分查找怎么做(因为该题是在二分查找分类下的)。

    那对什么东西来应用二分查找呢?
    1、是对在m个子串的条件下,来找寻(m-1)个cut的位置上应用二分查找吗?如果是那样的话,二分查找的起始边界是什么,跳出查找的循环条件又是什么?显然这两个问题是很难具体确认的,我们应该再想想其他的。
    2、本题最终需返回的是最小子串划分方法的最大子串值(我把它叫做minmax),那要不我们直接对这个值应用二分查找?那二分查找的起始边界是什么,跳出查找的循环条件又是什么呢?欸,这个思路似乎可行,我们具体来看(见下文)。

  • 具体分析

    通过来回答以下的问题,来更清晰的认识代码该怎么写。

    1、minmax的可能的最大最小值是多少?
      最小值(low或left的初值):nums数组中的最大元素值。
      最大值(high或right的初值):nums数组中所有元素的和。
      举例,nums=[2,3,7,5,9],则minmax的最小值为9,最大值为26

    2、如何缩小范围?也即low、high改变取值的条件(low=mid+1,high=mid-1)是什么?
      这里是算法的核心部分。
      若用一句话总结,便是看在进行子串和不大于mid的分割后,看分割的次数和m-1的关系。
      什么叫进行子串和不大于mid的分割?
      我们举个例子,nums=[2,3,7,5,9],第一轮mid=(low+high)/2=17。我们从左边的第一个数开始累加,subSum=2+3+7+5=17,当再加上9时,sumSum=17+9=26,已经大于mid,所以在5和9之间应该割一刀,9作为第二个子串的第一个元素。在本轮中,分割次数=1.
      如何判断分割次数和m-1的关系呢?
      如果分割次数>m-1,说明我们按mid来尽可能地让每个子串足够长,也不能在m-1的次数(包括m-1)内结束分割,mid的值取得太小了,所以应该low=mid+1,进行下一轮的判断。
      如果分割次数 <=m-1,说明我们可以在m-1的次数(包括m-1)内结束分割,mid的值取得合理。但该mid的值一定就是最小情况下的最大值吗?不一定!(举个例子,nums=[2,3,7,5,9],假设m=2,当mid=17时,分割次数==m-1=1,但我们知道最终的答案可以更小,为14)所以,我们就试试去更小的值看看,即high=mid-1,进入下一轮的判断。

    3、结束循环的条件是什么?
      根据问题2中所述,若mid取得太小而导 致进行了大于m-1次的分割,我们会low=mid+1来向上缩小范围;若mid取得值可以进行了小于等于m-1次分割了,为了找到可能存在的更小mid,我们会令high=mid-1向小缩小范围。
      所以,该二分查找结束循环的条件是low=high,无中途break跳出的情况。

    4、可能存在的疑惑
      你可能会问,“为什么分割次数小于m-1而非等于,你依然会觉得mid的值是合理的呢?”
      因为该分割次数是从左往右,让每个子串尽可能的长而得来的。而事实上,我们求取的结果并不需要让每个子串尽可能长才可以。若分割次数小于m-1,那我们随便补上几刀就能使分割次数=m-1了。如nums=[2,3,1,1,1,1,1],mid=3,m=5,按照算法分割出的子串是[2],[3],[1,1,1],[1,1],那我可以在[1,1,1]上补一刀,变成[1,1]和[1],不就也m-1次分割了吗。
      这个合理的mid的值也不一定是最终结果,因为有可能还有比它小且合理的结果存在,所以我们要high=mid-1再看看。所以这个问题是和向下搜索更小的以及向下的底线(low)相辅相成的。

  • 代码

int judge(int* nums, int numsSize, int m,long mid){    int i,count;   //count为分割的次数,等于子串数-1    long subSum;    subSum=0;    count=0;    for(i=0;i<numsSize;i++){        subSum+=nums[i];        if(subSum==mid){            //从下一个元素开始下一个子串的构建            subSum=0;            if(i!=numsSize-1)                count++;        }        else if(subSum>mid){            //从本元素开始下一个子串的构建            count++;            subSum=nums[i];        }    }    if(count>m-1)        return true;    else        return false;}int splitArray(int* nums, int numsSize, int m) {    int l,max;    long r,sum,mid;    int i;    //获得l、r的初值    max=nums[0];    sum=0;    for(i=0;i<numsSize;i++){        if(nums[i]>max)            max=nums[i];        sum+=nums[i];    }       l=max;    r=sum;    while(l<=r){   //所有可能值已搜索完,l是最小的且满足条件的值        mid=(l+r)/2;        if(judge(nums,numsSize,m,mid))            l=mid+1;    //mid取的太小,向上缩小范围        else            r=mid-1;    //mid的值可能是结果,但有可能还有比它小且满足条件的值,所以向下缩小范围    }    return l;}

  • 运行时间
    如下图所示,
    这里写图片描述

  • 后话
    该题我一开始在写二分查找的时候,把分割次数等于m-1和分割次数小于m-1的情况拆开来写,认为只有分割次数等于m-1的才可能是答案,当意识到不一定要让每个子串都取足够长也可能是答案时,代码被改得混乱不堪,最终决定重写,才得到上面的代码。得到一个启发,分析问题要多留心可能的情况,把该合并的情况合并了,代码也就会写得比较容易了。

原创粉丝点击