01-复杂度1. 最大子列和问题

给定K个整数组成的序列{ N1, N2, ..., NK },“连续子列”被定义为{ Ni, Ni+1, ..., Nj },其中 1 <= i <= j <= K。“最大子列和”则被定义为所有连续子列元素的和中最大者。例如给定序列{ -2, 11, -4, 13, -5, -2 },其连续子列{ 11, -4, 13 }有最大的和20。现要求你编写程序,计算给定整数序列的最大子列和。

输入格式:

输入第1行给出正整数 K (<= 100000);第2行给出K个整数,其间以空格分隔。

输出格式:

在一行中输出最大子列和。如果序列中所有整数皆为负数,则输出0。

输入样例:
6
-2 11 -4 13 -5 -2
输出样例:
20  

分析:最自然的想法就是枚举始终位置,如下程序,但是复杂度O(n^3),对于某些测试点超时。

#include<stdio.h>
#define MAXN 100000
int arr[MAXN+];
int main(void)
{
int i, j, k, n, thisSum, maxSum = ; scanf("%d", &n);
for(i = ; i < n; i++)
scanf("%d", &arr[i]);
for(i = ; i < n; i++)
for(j = i; j < n; j++)
{
thisSum = ;
for(k = i; k <= j; k++)
thisSum += arr[k];
if(thisSum > maxSum)
maxSum = thisSum;
}
printf("%d\n", maxSum);
return ;
}

既然超时了,就想办法优化一下呗,容易观察到,对于某个固定的i, 变化的j(从i 开始),上面的程序反复计算从下标i 到下标j 的和,这是没有必要的,存在多次重复计算,因此可以如下改写:复杂度O(n^2)。

#include<stdio.h>
#define MAXN 100000
int arr[MAXN+];
int main(void)
{
int i, j, n, thisSum, maxSum = ; scanf("%d", &n);
for(i = ; i < n; i++)
scanf("%d", &arr[i]);
for(i = ; i < n; i++)
{
thisSum = ;
for(j = i; j < n; j++)
{
thisSum += arr[j];
if(thisSum > maxSum)
maxSum = thisSum;
}
} printf("%d\n", maxSum);
return ;
}

看到这里,你可能想到了求序列的前n 项和来解决这个问题,我们令Si 为序列的前i 项(包括i)和。那么下标从i 到j 的连续子序列和为Ai + Ai+1 + ... + Aj = Sj - Si-1
注意到,前面我故意模糊了i 的取值,是因为我们得借助表达式Sj - Si-1 来定界。显然,为了得到我们想要的结果,Sj - Si-1 必须的遍历每一个子序列,得到界限:1 <= i <= n, i ≤ j ≤ n。即S0 = 0,这需要在程序中体现出来,不然肯定WA。复杂度仍然是O(n^2),程序如下:

#include<stdio.h>
#define MAXN 100000
int arr[MAXN+];
int main(void)
{
int i, j, n, thisSum, maxSum = ; scanf("%d", &n);
for(i = ; i <= n; i++)
scanf("%d", &arr[i]); arr[] = ;
for(i = ; i <= n; i++)
arr[i] += arr[i-]; // 递推前n项和
for(i = ; i <= n; i++)
for(j = i; j <= n; j++)
{
thisSum = arr[j] - arr[i-];
if(thisSum > maxSum)
maxSum = thisSum;
}
printf("%d\n", maxSum);
return ;
}

难道我们就摆脱不了n^2 的噩梦?怪姥姥告诉我们说:想成为一个优秀的Coder,看到n^2 就得下意识的想到能不能把n^2 降阶,什么nlogn 啦,甚至n 什么的。考虑到二分时复杂度为logn,因此我们可以来个移花接木,借用其思想,换句话说,可以用分治法。分:二分,递归。治:治理。如果采用二分的思想的话,考虑连续子序列最大和可能出现的位置有三种,二分的左侧、右侧或是跨越两者边界的情况,因此有:

#include<stdio.h>
#define MAXN 100000
int maxSeqSum(int arr[], int low, int high);
int maxOfThree(int a, int b, int c);
int arr[MAXN+];
int main(void)
{
int i, n; scanf("%d", &n);
for(i = ; i < n; i++)
scanf("%d", &arr[i]); printf("%d\n", maxSeqSum(arr, , n-));
return ;
} int maxSeqSum(int arr[], int low, int high) // 左闭右闭区间
{
int i, mid, max1, max2, thisSum, maxLeftSum, maxRightSum; // printf("%d %d\n", low, high); 测试用的语句,正如下面优先级那里说的,不用该行捕获输出的话,输入完数据程序就退出了,怎生死的都不知道.
if(low == high)
return arr[low]; mid = low + ((high-low) >> ); // 优先级+bigger than>>,这个错误不止出现一次了Orz,如果不加括号的话,由于无限递归导致堆栈溢出将发生段错误
max1 = maxSeqSum(arr, low, mid);
max2 = maxSeqSum(arr, mid+, high); thisSum = maxLeftSum = ;
for(i = mid; i >= low; i--)
{
thisSum += arr[i];
if(thisSum > maxLeftSum)
maxLeftSum = thisSum;
}
thisSum = maxRightSum = ;
for(i = mid+; i <= high; i++)
{
thisSum += arr[i];
if(thisSum > maxRightSum)
maxRightSum = thisSum;
} return maxOfThree(max1, max2, maxLeftSum+maxRightSum);
} int maxOfThree(int a, int b, int c)
{
int max = a;
if(b > max)
max = b;
if(c > max)
max = c;
return max;
}

有没有发现,代码风格我采用的Java 的。编码的时候很慢,因为我经常琢磨怎么命名,下划线分隔的、所有首字母大写的、瞎搞的不一而足。最终还是采用了Java 风格的变量命名法。既然刚才提到nlogn 了,我们就来证明下,该程序的时间复杂度为O(nlogn)。首先,此程序的时间复杂度显然和输入规模有关,因此我们令对n 的输入规模耗时为T(n)。那么显然有T(n) = 2T(n/2) + n; 即T(n) = 2T(n/2) + n = 2[2T(n/4) + n/2] + n = ... 不断迭代下去容易得到形式T(n) = 2kT(n/2k) + kn。令2^k = n,则T(n) = n + nlogn = O(nlogn)。[注:这里只对n 是2 的幂做了讨论,不过其他的值和该方法分析的结果不会有偏差,我们可以规约吗:)]。然而O(nlogn)就是时间优化的极限了吗?很遗憾,不是的,还有更强的,下面是世间复杂度为O(n)的暴强版:

#include<stdio.h>
#define MAXN 100000
int arr[MAXN+];
int main(void)
{
int i, n, thisSum, maxSum = ; scanf("%d", &n);
for(i = ; i < n; i++)
scanf("%d", &arr[i]); thisSum = ;
for(i = ; i < n; i++)
{
thisSum += arr[i];
if(thisSum > maxSum)
maxSum = thisSum;
if(thisSum < )
thisSum = ;
} printf("%d\n", maxSum);
return ;
}

其思想很巧妙,就是thisSum一旦小于零,就重置其值为0。因为用一个序列和小于零的序列在拼接后面的数是没有意义的,它永远不会是最大的。最后,还有更快的吗?不好意思,没有了,这是最快的了,没有更快的了,因为该算法是在线的,想要求最大和至少得读一遍数据吧,而光读数据就得花费O(n) 的时间,没有人会认为还没扫描完数据就能求出最大和吧。

上面贴了五个程序,为了直观的看到它们时间复杂度的不同,感悟思维的魅力,我把各测试点的用时记录在下面的表格里:

测试点 程序1用时(ms) 程序2 程序3 程序4 程序5
0 1 1 1 1 1
1 1 1 1 1 1
2 126 1 1 1 1
3 TLE 50 50 2 2
4 TLE 4854 4857 12 9

01-复杂度2. Maximum Subsequence Sum (25)

Given a sequence of K integers { N1, N2, ..., NK }. A continuous subsequence is defined to be { Ni, Ni+1, ..., Nj } where 1 <= i <= j <= K. The Maximum Subsequence is the continuous subsequence which has the largest sum of its elements. For example, given sequence { -2, 11, -4, 13, -5, -2 }, its maximum subsequence is { 11, -4, 13 } with the largest sum being 20.

Now you are supposed to find the largest sum, together with the first and the last numbers of the maximum subsequence.

Input Specification:

Each input file contains one test case. Each case occupies two lines. The first line contains a positive integer K (<= 10000). The second line contains K numbers, separated by a space.

Output Specification:

For each test case, output in one line the largest sum, together with the first and the last numbers of the maximum subsequence. The numbers must be separated by one space, but there must be no extra space at the end of a line. In case that the maximum subsequence is not unique, output the one with the smallest indices i and j (as shown by the sample case). If all the K numbers are negative, then its maximum sum is defined to be 0, and you are supposed to output the first and the last numbers of the whole sequence.

Sample Input:
10
-10 1 2 3 4 -5 -23 3 7 -21
Sample Output:
10 1 4

分析:该题目是04年浙大考研复试真题,是第一个题目的简单的变形,对于上面五个程序中时间复杂度为O(n) 的程序很好改写,对于O(nlogn) 的那个略难。

改写O(n):

#include<stdio.h>
#define MAXN 100000
int arr[MAXN+];
int main(void)
{
int n, i, thisSum, maxSum, begin, end, index; scanf("%d", &n);
for(i = ; i < n; i++)
scanf("%d", &arr[i]); thisSum = maxSum = index = ;
for(i = ; i < n; i++)
{
thisSum += arr[i];
if(thisSum > maxSum)
{
maxSum = thisSum;
begin = index;
end = i;
}
if(thisSum < )
{
thisSum = ;
index = i+;
}
} if(maxSum == )
{
for(i = ; i < n; i++)
{
if(arr[i] == )
break;
}
if(i == n) // 所有元素都是负数
printf("%d %d %d\n", , arr[], arr[n-]);
else
printf("0 0 0\n");
}
else
printf("%d %d %d\n", maxSum, arr[begin], arr[end]);
return ;
}

改写O(nlogn):

#include<stdio.h>
#define MAXN 100000
int MaxSeqSum(int arr[], int low, int high);
int MaxOfThree(int a, int b, int c);
int arr[MAXN+];
int G_MAX = ;
int G_LeftIndex = ;
int G_RightIndex = ;
int main(void)
{
int i, n, ans; scanf("%d", &n);
for(i = ; i < n; i++)
scanf("%d", &arr[i]); ans = MaxSeqSum(arr, , n-);
printf("%d", ans);
if(ans == )
{
for(i = ; i < n; i++)
if(arr[i] == )
break; if(i == n)
printf(" %d %d", arr[], arr[n-]);
else
printf(" %d %d", , );
}
else
printf(" %d %d", arr[G_LeftIndex], arr[G_RightIndex]);
return ;
} int MaxSeqSum(int arr[], int low, int high) // 左闭右闭区间
{
int i, leftBoundIndex, rightBoundIndex, mid;
int max1, max2, max3, thisSum, maxLeftSum, maxRightSum, localMax; if(low == high)
{
if(arr[low] <= )
return ;
else
return arr[low];
} mid = low + ((high-low) >> ); // 注意优先级,这里栽了好几次了Orz...
max1 = MaxSeqSum(arr, low, mid);
max2 = MaxSeqSum(arr, mid+, high); thisSum = maxLeftSum = ;
for(i = mid; i >= low; i--)
{
thisSum += arr[i];
if(thisSum > maxLeftSum)
{
maxLeftSum = thisSum;
leftBoundIndex = i;
}
}
thisSum = maxRightSum = ;
for(i = mid+; i <= high; i++)
{
thisSum += arr[i];
if(thisSum > maxRightSum)
{
maxRightSum = thisSum;
rightBoundIndex = i;
}
} max3 = maxLeftSum + maxRightSum;
localMax = MaxOfThree(max1, max2, max3); if(G_MAX < localMax)
{
G_MAX = localMax; if(max1 == G_MAX)
{
G_LeftIndex = G_RightIndex = low;
}
else if(max3 == G_MAX)
{
if(max3 == max2) // 左边界未初始化,为垃圾值,至于右边界的问题在第一个if里已经得到了处理
G_LeftIndex = rightBoundIndex;
else
G_LeftIndex = leftBoundIndex; G_RightIndex = rightBoundIndex;
}
else
{
G_LeftIndex = G_RightIndex = high;
}
} // printf("%d %d\n", low, high); // test
// printf("%d %d\n", G_LeftIndex, G_RightIndex); return localMax;
} int MaxOfThree(int a, int b, int c)
{
int max = a;
if(b > max)
max = b;
if(c > max)
max = c;
return max;
}

(END_XPJIANG)

PAT复杂度_最大子列和问题、最大子列和变种的更多相关文章

  1. dataframe的进行json数据的压平、增加一列的id自增列

    {"name":"Michael", "age":25,"myScore":[{"score1":1 ...

  2. MySQL增加列,修改列名、列属性,删除列

    mysql修改表名,列名,列类型,添加表列,删除表列 alter table test rename test1; --修改表名 alter table test add  column name v ...

  3. Mysql有没有语法可以在增加列前进行判断该列是否存在

    Mysql没有直接的语法可以在增加列前进行判断该列是否存在,需要写一个存储过程完成同样任务,下面例子是:在sales_order表中增加一列has_sent列 drop procedure if ex ...

  4. 选择列表中的列无效,因为该列没有包含在聚合函数或 GROUP BY 子句中

    选择列表中的列无效,因为该列没有包含在聚合函数或 GROUP BY 子句中 T-SQL核心语句形式: SELECT     --指定要选择的列或行及其限定  [INTO ]      --INTO子句 ...

  5. c# datagridview与DataSet绑定, 列与数据库表里面的列一一对应

    参考代码1: 自己模拟出数据,并分别对dataGridView赋值. using System; using System.Collections.Generic; using System.Comp ...

  6. 【SQL】Update中使用表别名、如何用表中一列值替换另一列的所有值

    Update中使用表别名 select中的表别名: select * from TableA as ta update中的表别名: update ta from TableA as ta 如何用表中一 ...

  7. pig加载两个不同字段个数的文件?load file with different items(f1有42列,f2有43列读到一个对象中)

    我文章提到,加载一个文件的部分列是可行.两列,你只读一列,没问题. 但是,两个文件,f1和f2,f1有42列,f2有43列,同时加载到一个流对象,如何? 答:成功加载.但是无结构(schema unk ...

  8. 如何在Delphi 中使用 DevExpressVCL的 CxGrid与CxTreeList,编辑某列后计算另一列的值

    如何在Delphi 中使用 DevExpressVCL的 CxGrid与CxTreeList,编辑某列后计算另一列的值:比如 输入 单价,数量,计算金额. 参考: 1.  输入 单价,数量,计算金额 ...

  9. mysql 增加列,修改列名、列属性,删除列语句

    mysql增加列,修改列名.列属性,删除列语句 mysql修改表名,列名,列类型,添加表列,删除表列     alter table test rename test1; --修改表名 alter t ...

随机推荐

  1. c++2008 并行配置文件和获取字典的所有key的方法

    1 需要 在官网 下载对应的执行包... 2, # !/usr/bin/python3.4 # -*- coding: utf-8 -*- b = { 'video':0, 'music':23 } ...

  2. hibernate模糊查询

    hibernate模糊查询-Restrictions.ilike & Expression.like Criteria criteria = session.createCriteria(Ta ...

  3. 团队Git工作流总结

    为什么使用Git “svn用了这么多年都好好的,为啥折腾搞Git?” “Git一点都不好用,提交个代码都提交不上去!” “Git这么复杂,命令多到记不住,而且完全用不到.哪有svn简单好用?”   推 ...

  4. 单元测试地二蛋 先弄个两个原生模块1个原始的一个jq插件

    放羊测试测完了再测这两个瞎搞的下拉列表组建 看看从单元测试模块化的角度组建会写成啥样 1:ajax请求 简单文本     2:1个页面多个实例     3:复杂展示+自定义点击+自定义处理函数     ...

  5. iscrolljs 看API 回顾以前开发中失误

    今天有空 细致的看看iscrolljs api 发现自己以前的几个失误是没看api造成的 失误1 页面a操作 影响了页面b的滚动条 api 解释: options.bindToWrapper The ...

  6. YUM源设置

    1挂载光盘 先创建一个文件 /aaa 然后挂载mount /dev/cdrom /aaa 进入 /aaa   ls 查看是否挂载OK 2进入yum文件夹.将除Media以外的所有文件名改为XXXXXX ...

  7. jquery ui dialog autofocus 去掉默认第一个元素获取焦点

    经常在dialog窗口中第一个元素为日期控件时,打开窗口则会自动显示日期下拉框. 解决办法:在dialog的open事件中,设置父对象获得焦点. p1_dialog_seniorSearch.dial ...

  8. javascript平时例子⑧(大屏轮播)

    <!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>& ...

  9. 20145205 《Java程序设计》第7周学习总结

    教材学习内容总结 认识时间与日期 1.格林威治时间(GMT):通过观察太阳而得,因为地球公转轨道为椭圆形且速度不一,本身自传减速而造成误差. 2.世界时(UT):通过观测远方星体跨过子午线而得,受地球 ...

  10. WM_COPYDATA进程间通信方案

    连续在两个公司使用WM_COPYDATA实现进程间通信了,整理一下 具体步骤: 一.   进程A通过ShellExecute启动进程B, 将用于通信的窗口句柄hWndA(已强转为int值)通过命令行参 ...