P1115 最大子段和
题目描述
给出一段序列,选出其中连续且非空的一段使得这段和最大。
输入格式
第一行是一个正整数N,表示了序列的长度。
第二行包含NN个绝对值不大于10000的整数Ai,描述了这段序列。
输出格式
一个整数,为最大的子段和是多少。子段的最小长度为1。
输入输出样例
说明/提示
【样例说明】
2,-4,3,-1,2,-4,3中,最大的子段和为4,该子段为3,-1,2.
【数据规模与约定】
对于40%的数据,有N≤2000。
对于100%的数据,有N≤200000。
各个方法介绍:
纯暴力解法:
这个方法没什么好说的,直接枚举边界然后在累加每一个,判断和是否为最大,最大替换即可,算法时间复杂度O(N3)。
代码:
#includeint a[200000];int main (void){ int n, i, j, sum, max, start; max = -1e9; scanf("%d", &n); for(i = 0; i < n; i++) scanf("%d", &a[i]); for(i = 0; i < n; i++) for(j = i; j < n; j++) { sum = 0; for(start = i; start <= j; start++) sum += a[start]; if(sum > max) max = sum; } printf("%d", max); return 0;}
结果:
暴力优化:
由于遍历范围的头尾两个的位置时,尾部要从头开始每次循环完递增,所以,我们实际上可以在递增时把值加起来,判断是否最大,算法时间复杂度O(N2)
代码:
#includeint a[200000];int main (void){ int n, i, j, sum, max, start; max = -1e9; scanf("%d", &n); for(i = 0; i < n; i++) scanf("%d", &a[i]); for(i = 0; i < n; i++) { sum = 0; for(j = i; j < n; j++) { sum += a[j]; if(sum > max) max = sum; } } printf("%d", max); return 0;}
结果:
分治法:
所谓分治,就是强行把数拆成两半,那么,我们易得,该最大子序列一定是在前半段的最大子序列或者后半段的最大子序列或者是前半段子序列末尾为前半段范围末尾的最大子序列与后半段子序列开头为后半段范围开头的最大子序列之和,只可能是这三种情况。我们采用递归来实现分治,每次程序返回的都是在该段内最大的子序列,最后就能实现在整段内找到最大的子序列。我们人为的把数组拆成两半,通过递归得到左段和右段的最大值,然后从该段中间向两边遍历,找到从该范围内中间到左边和中间+1到右边的最大子序列,并把两者相加,并把其与左右段最大子序列进行比较并返回最大即可得到该段中最大的子序列。算法时间复杂度O(NlogN)。
代码:
#includeint a[200000], max;int maxn(int x, int y, int z){ if(x >= y && x >= z) return x; else if(y >= x && y >= z) return y; else return z;}int divide(int left, int right){ if(left == right) return a[left]; int l = 0, r = 0, maxlt = -1e9, maxrt = -1e9;//maxrt为从中间后一个开始向右最大的子序列,maxlt从中间向左的最大 int mid = (right + left) / 2; int maxl = divide(left, mid );// maxl为左段最大值 int maxr = divide(mid + 1, right);//maxr为右端最大值 for(int i = mid; i >= left; i--) { l += a[i]; if(l > maxlt) maxlt = l; } for(int i = mid + 1; i <= right; i++) { r += a[i]; if(r > maxrt) maxrt = r; } return maxn(maxl, maxr, maxlt + maxrt);}int main (void){ int n, i, j; scanf("%d", &n); for(i = 0; i < n; i++) scanf("%d", &a[i]); max = divide(0, n - 1); printf("%d", max); return 0;}
结果:
动态规划:
本题将序列从开头开始遍历,相当于序列的开头为数组的开头,序列的末尾从开头一直到末尾,每次得到的结果为该范围内最大的子序列。一开始最大子序列和就是a[0](因为序列开头和结尾都是第一个)。当一个序列中找到最大子序列后,读取下一个数,max要么是之前序列的最大值,要么是之前序列中子序列的末尾是之前序列末尾的最大子序列加上刚刚读取的数,要么就是刚刚读取的数。如果之前序列中子序列的末尾为之前序列末尾的最大子序列小于0,那么该子序列加上刚刚读取的数肯定没有刚刚读取的数大,如果大于等于0,则该子序列加上刚刚读取的数要比刚刚读的数大,所以两者最大与之前序列最大的子序列之和比较,得到现在的最大子序列和,当到达末尾时,也就是这个数组的最大子序列。声明一个sum累加数,每加一个数都要判断sum是否比max要大,如果大就替换,如果sum < 0, 那么说明从此时的开始累加比此时的数加上之前累加出来的负值要大,因此将sum置为0(表明从此时开始算子序列,sum的值为当前的数),sum的值即是从当前位置向左得到的子序列中和最大的,因而与max进行比较。算法时间复杂度O(N)。
代码:
#includeint a[200000], sum, max;int main (void){ int n, i; max = -1e9; scanf("%d", &n); for(i = 0; i < n; i++) { scanf("%d", &a[i]); if(sum < 0)//说明之前的序列中,子序列末尾为之前序列末尾的最大子序列之和为负数,加a[i]还不如a[i]大,所以置为0 sum = 0; sum += a[i]; if(sum > max) max = sum; } printf("%d", max); return 0;}
结果:
其实采用dp的方法并不需要开数组进行存储,这样既节省了空间也节省了时间,这里只需要把a[i]换成一个变量即可(就不贴代码了)