最大子数组和最大值平均子数组

1.最大子数组

给定一个整数数组,找到一个具有最大和的子数组,返回其最大和。

样例

给出数组[−2,2,−3,4,−1,2,1,−5,3],符合要求的子数组为[4,−1,2,1],其最大和为6

法1:贪心

  int maxSubArray(vector<int> nums) {
        // write your code here
        int sum=nums[0];
        int t=sum;
        for(int i=1;i<nums.size();i++)
        {
            if(t<0)t=0;
            t=t+nums[i];
            if(t>sum)sum=t;
            
        }
        return sum;
    }

法2:分治

class Solution {
public:    
    /**
     * @param nums: A list of integers
     * @return: A integer indicate the sum of max subarray
     */
    int maxSubArray(vector<int> nums) {
        // write your code here
        return m(nums,0,nums.size()-1);
    }
   int cross(vector<int>nums,int start,int mid,int end)
    {
        int leftmax=0;
        int sum=INT_MIN;
        for(int i=mid;i>=start;i--)
        {
            leftmax+=nums[i];
            if(sum<=leftmax)sum=leftmax;
        }
        leftmax=sum;
        int rightmax=0;
        sum=INT_MIN;
        for(int i=mid+1;i<=end;i++)
        {
            rightmax+=nums[i];
            if(sum<=rightmax)sum=rightmax;
        }
        sum+=leftmax;
        return sum;
        
    }
    int m(vector<int>nums,int start,int end)
    {
        if(start==end)return nums[start];
        int mid=(start+end)/2;
        int leftmax=m(nums,start,mid);
        int rightmax=m(nums,mid+1,end);
        int middlemax=cross(nums,start,mid,end);
        if(leftmax>=rightmax&&leftmax>=middlemax)return leftmax;
        else if(middlemax>=rightmax&&middlemax>=leftmax)return middlemax;
        else return rightmax;
    }
};

法三:暴力(略)

2.最大平均子数组

给出一个整数数组,有正有负。找到这样一个子数组,他的长度大于等于 k,且平均值最大。

样例

给出 nums = [1, 12, -5, -6, 50, 3], k = 3

返回 15.667 // (-6 + 50 + 3) / 3 = 15.667


引用他人的思路

1、一个数组的子数组的最大平均数一定在数组的最大值和最小值之间,所以二分法的第一步限定average位于[min,max]之中。

2、接下去要做的就是不断的缩小范围,直至max-min足够小(如1e-6),那我们就得到了想要的结果。

缩小范围的思想如下:

每一轮设置mid=(min+max)/2,然后将原数组中的每一个数减去这个mid,如果能找到(经过提醒,改正为:大于等于)k个相邻数的总和大于0的情况,那么说明最终结果一定比这个mid要更大,限定下一轮寻找范围在[mid,max]之中。反之在限定在[min,mid]之中。

那么在实际算法中我们需要解决的关键一步就是,如何判断“有(大于等于)k个相邻数的总和大于0的情况存在”。

首先,我们可以用sum数组来存储减掉mid值的原数组的各总和(sum[i]存储num[0]-mid到num[i-1]-mid的总和),当sum[i]存储的总和个数超过k时(即i>k),也就是说我们保证了这个子数组的长度达到k后,可以砍掉之前一些拖后腿的数。这些拖后腿的数在上述链接的代码中是用min_pre来实现的。当之前拖后腿的数值小于min_pre时,更新min_pre=sum[i - k + 1]。sum[i]存储的是num[0]~num[i-1]减去mid的总和,而min_pre存储的是num[0]~num[k]减掉mid的总和,这样sum[i]-min_pre得到的是sum[k+1]~sum[i-1],它所记录的总和个数也就是到num[i]为止能够找到的最大平均数 子数组的长度。


代码:

class Solution {
public:
    /**
     * @param nums an array with positive and negative numbers
     * @param k an integer
     * @return the maximum average
     */
 
    double maxAverage(vector<int>& nums, int k) {
        // Write your code here
        double start=INT_MAX;
        double end=INT_MIN;
        for(int i=0;i<nums.size();i++)
        {
            if(nums[i]<=start)start=nums[i];
            if(nums[i]>=end)end=nums[i];
        }
       
        int len=nums.size();
        double sum[len+1];
        memset(sum,0,sizeof(sum));
        while(end-start>1e-6)
        {
            double mid=(start+end)/2;
            double min_pre=0;
            bool flag=false;
            for(int i=1;i<=nums.size();i++)
            {
                sum[i]=sum[i-1]+nums[i-1]-mid;
                if(i>=k&&sum[i]-min_pre>=0)
                {
                    flag=true;
                    break;//只要有一组满足平均值大于mid就可以跳出内循环
                }
                if(i>=k)
                {
                    min_pre=min(min_pre,sum[i-k+1]);
                }
            }
            if(flag)start=mid;
            else end=mid;
        }
        return start;
    }
};

3.通过做第二道题我又发现了一个做第一道题的方法

int maxSubArray(vector<int> nums) {
        // write your code here
        int sum[nums.size()+1]={0};
        int min_pre=0;
        int maxn=INT_MIN;
        for(int i=1;i<=nums.size();i++)
        {
            sum[i]=sum[i-1]+nums[i-1];
            if(sum[i]-min_pre>=maxn)maxn=sum[i]-min_pre;
            min_pre=min(sum[i],min_pre);
        }
        return maxn;
}
但仔细一想 其实这种方法就是用sum数列的最大值减去sum数列的最小值,第二题也是如此,就是用当前值减去最小值,在遍历的过程中更新最小值,最终得到正确的答案



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值