对于典型局部求和问题的思考

一.算法题干

你的好友是一位健身爱好者。前段日子,他给自己制定了一份健身计划。现在想请你帮他评估一下这份计划是否合理。
他会有一份计划消耗的卡路里表,其中calories[i]给出了你的这位好友在第i天需要消耗的卡路里总量。
为了更好地评估这份计划,对于卡路里表中的每一天,你都需要计算他 「这一天以及之后的连续几天」 (共k天)内消耗的总卡路里T
如果T < lower,那么这份计划相对糟糕,并失去1分;
如果T > upper,那么这份计划相对优秀,并获得1分;
否则,这份计划普普通通,分值不做变动。
请返回统计完所有calories.length天后得到的总分作为评估结果。
注意:总分可能是负数。

二.解题思路

这道题我一开始的解题思路是:对数组中每一个元素,都取连续的k个数进行求和,然后进行剩余操作,若最后剩余不足k个数则结束循环。具体代码如下:

int dietPlanPerformance(vector<int>& c, int k, int lower, int upper) {
	int sum=0;
    for(int i=0;i<c.size()&&i+k-1<c.size();++i)
    {
    	int ts=0;
        for(int j=0;j<k;++j)
        {
        	ts+=c[i+j];
        }
        if(ts<lower) --sum;
        else if(ts>upper) ++sum;
    }
    return sum;
}

但是这个代码提交上去后,通过了26个测试样例,仅有一个样例没有通过,具体症状如下图所示。
样例超时
可以看到,唯一没有通过的那个样子是专门卡时间的,这说明我的算法复杂度还不够低。我算了一次,针对我的算法,T(n)=O(n^2),在数组长度和k的数值给的较大时,时间表现并不好。所以我就考虑换了一种方法。
在新的方法中,我利用k这个不变量,维护了一个固定长度滑动窗口,在数组上进行滑动,每次移动即“左减右加”,也就是将当前窗口的局部和减去左边“滑出”的值,加上右边“滑入”的值,以得到新的局部和。这种思路就避免了针对每个起点的遍历操作,降低了时间复杂度。

三.实现代码

int dietPlanPerformance(vector<int> &c,int k,int lower,int upper) {
	int sum=0,ts=0;
	for(int i=0;i<k;++i) tc+=c[i];
	if(ts<lower) --sum;
	else if(ts>upper) ++sum;
	for(int i=1;i<c.size()&&i+k-1<c.size();++i)
	{
		ts=ts-c[i-1]+c[i+k-1];
		if(ts<lower) --sum;
		else if(ts>upper) ++sum;
	}
	return sum;
}

四.对比分析

int dietPlanPerformance(vector<int>& calories, int k, int lower, int upper) {
	int n = calories.size();
    vector<long long> sums(n + 1, 0);
    
    for (int i = 0; i < n; i++) sums[i + 1] = sums[i] + calories[i];
    
    long long points = 0;

	for (int i = 0; i + k <= n; i++) {
		long long sum = sums[i + k] - sums[i];

		if (sum < lower) points--;
		
		if (sum > upper) points++;
	}
	
    return points;
}

从这段代码中,我主要学习到了两点:
第一点是语法层面的,我知道了可以通过指定两个参数来构造一个具有固定长度的vector。其中,第一个参数是初始元素的个数,第二个参数是初始元素值。
第二点是技巧层面的。该代码当中巧妙地使用到了前缀和的思想。所谓的前缀和,就是维护一个大小为n+1的数组,这个数组中第0个元素的值为0,第i个元素的值是原始数组从开头到第i-1个元素的累加和。这个数组的应用范围很广,只要是涉及到局部范围求和,且左右端点的变化是相对有规律的,那么就可以使用前缀和来尝试进行解决该类问题。

五.题目来源

健身计划评估

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值