题目:最喜爱的序列
小唐这段时间在研究序列。拿来N个整数的序列,他给序列中的每个整数都赋予一个喜爱值。喜爱值也是整数,有正有负,越大表明越喜欢。他想知道,如何从序列中连续取最多m个数,他获得喜爱值最大。1≤N≤500000,1≤m≤N。
输入格式
第一行是两个整数N,m。分别代表序列中数的个数以及能取的最多个数。 第二行用空格隔开的N个整数,第i个整数Li代表他对第i个数的喜爱值。│Li│≤1000
输出格式
一行,三个数,表示获得最大喜爱值,及第一个取最大喜爱值的区间。
输入样例
输出样例
|
|
题目解析
这个问题本质上就是求最大连续子数列的和,其中连续取最多m个整数限制了区间的范围
方法一:暴力法
暴力法解决这个问题无非就是遍历所有可能的子序列并计算其和 用嵌套的for循环来实现遍历。 代码实现
|
|
分析:
使用暴力法的时间复杂度显然是O(n*m)的,这实在太慢了😅,*虽然还有更慢的* 很容易被时间限制卡住。
方法二:单调队列
要使用单调队列,我们需要引入一个前缀和序列。 维护这样一个前缀和的单调队列,区间范围为m,则队列首元素为该区间中的最小值,用区间内的前缀和去减去这个最小值,则得到值最大的,为该序列中子序列的最大值 举个栗子
|
|
代码实现
|
|
分析
因为每个前缀和序列中的元素只被访问了一次,时间复杂度是O(n),可以看到相比暴力穷举法,时间效率大大的提高了。如果使用数组自己维护双端队列 则空间还可以节省三分之一 我是five懒得写了
方法三:分治法
总的来说就是分三种情况来分治
找到左半部分的最大子序列的和
找到右半部分的最大子序列的和
找到中间跨边界的最大子序列的和
- 向左一步,计算和,若大于上一步的和则更新
- 向右一步,计算和,若大于上一步的和则更新
- 将左侧的和与右侧的和相加得到中间的和
代码实现
真的写不出能分治出定长区间和区间端点的😢
|
|
算法分析
最坏时间复杂度为O($n^2$)阶的,因为最终每个递归树叶子节点都遍历了全部链表并没有比优化后的暴力好
最优时间复杂度为O(n),每个叶子节点都只遍历了一遍
期望时间复杂度应该是O($n\log n$)
方法四:动态规划
用动态规划的思路去思考问题,每一步决策无非就是,是否将下一个数加入到当前的序列中 因为我们只求和,所以只需要用一个变量来存放当前序列的和 为什么可以这样做? 因为和最大的连续子序列拥有最优子结构的特性 例如在[-1,4,5,-10,3]中 [4,5]是和最大的连续子序列 而[4]也是在[-1,4]中和最大的连续子序列。 代码实现
|
|
算法分析
很显然,整个算法只扫描了一遍序列,算法的时间复杂度是O(n)的,与单调队列一样。这个算法还有个名字叫kadane算法。用动态规划还是非常不错的,代码短速度快空间也省。就是不能很好的契合这道题😅
总结
总得来说求最大连续子序列和的问题有这样四种思路,各有优缺点,其中暴力法容易理解;单调队列适用场景广;分治法虽然在这里可行,但显然难以发挥其优势;而动态规划可以说没有缺点 (我是five,不知道能不能改成定长区间的)。所以在合适的场景选择合适的算法才是*ao*的。极力推荐,单调队列和动态规划。