PAT 1007 Maximum Subsequence Sum (最大子列和) 解法大全及规避坑点 C/C++版

求整数数列的最大子列和(含子列范围)问题:

本问题以PAT甲级1007为例:有多种解法,时间复杂度立方到线性都囊括了。包含如何规避坑点,多种思路求解。如有疑问可在评论区留言。欢迎大牛指正其中不足之处。

求解方法包括:以下三条分支

暴力定义法(立方时间)----> 累加求和法(平方时间)----> 最值查表法(线性时空) (我第一遍做此题的方法)

基于贪心思想动态规划法(线性时间,原地空间)  (第二次刷此题用的此法)

递归分治法(线性时空)(有师弟刚学了分治欲用此法说很难,笔者硬是写出来代码确实很复杂)


目录

求整数数列的最大子列和(含子列范围)问题:

1007 Maximum Subsequence Sum (25 分)

题目大意:

名词声明:

一、分析:

1.1 如果确保“若同大取两端下标小者”?

1.2 ★ 数列为全负如何处理?★

1.3 ★ 多种解法的统一模板(C++)★

二、根据定义,从暴力解法到查表 (dp)线性解法

2.1 定义暴力解法 时间O(K^3),空间O(K),不记Num空间O(1)

2.2  定义累加解法 时间O(K^2),空间O(K),不记Num空间O(1)

2.3  查表求和解法 时间O(K^2),空间O(K)

 2.4 ★ 查表求和求最值解法(线性时空)★

三、 ★贪心思想线性时空解法★

四、 递归分治法(线性时空)

后记


1007 Maximum Subsequence Sum (25 分)

OJ网址:https://pintia.cn/problem-sets/994805342720868352/problems/994805514284679168

Given a sequence of K integers { N​1​​, N​2​​, ..., N​K​​ }. A continuous subsequence is defined to be { N​i​​, N​i+1​​, ..., N​j​​ } 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

题目大意:

给出一数列N,长度为K,求其最大子列和及其首末元素。若有多个子列和同大,取两端下标小者。若数列为全负,定义其最大子列和为0,返回的首末元素为整个数列的首末元素。


名词声明:

本文代码变量取名尽可能与题目一致。

Num[]:表示待求数列的数组,题目中是以N为数列名,为区分大家习惯将N当做数组长度,故改为Num。注意:数列N的下标是从1开始的,而Num下标是从0开始。

K:题目中表示数列N的数量即Num的长度。

Sum[j]:表示数列N的前j项和,Sum[j+1]=\sum_{i=0}^{j}Num[i],特别的 Sum[0]= 0。

Sum(i,j+1)=\sum_{k=i}^{j}Num[k] = Sum[j+1]-Sum[i]

best、first、last:即题目要输出的最大子列和及首末元素。在遍历的过程中不断更新,用更优替换,而且三者同时处理。


一、分析:

根据题目定义,待求子列是在所有子列中求和最大者,若同大取两端下标小者。

1.1 如果确保“若同大取两端下标小者”?

子列和最大不难定义,求个和比比就是了。容易出错的是两端下标小者。

如:出现Sum(i+1,j-1)==Sum(i,j)的情况,是取左端下标小还是右端下标小呢?

放心如果出现这种情况,那么Sum(i+1,j-1)和Sum(i,j)都不可能是最大子列和,或者有下标更小的同大子列和。

∵ Sum(i,j)=Num[i]+Sum(i+1,j-1)+Num[j-1]

由此可得:0=Num[i]+Num[j-1]

若两者一正一负,如 Num[i]>0,则Sum(i,j-1)·是更大的子列和;同理若 Num[j-1]>0,则Sum(i+1,j)更优。

若都为0,Num[i]==Num[j-1]==0,则 Sum(i,j-1) 两端下标更小!

为确保“若同大取两端下标小者”,只要遍历时按i,j由小到大,同大不更新保留原有i,j即可。

 

1.2 ★ 数列为全负如何处理?★

题目:若数列为全负,定义其最大子列和为0,返回的首末元素为整个数列的首末元素。

这是此题的一个坑点。用笨办法就是录入数组时专门用个flag检查数组是否为全负,如果是,那就直接输出“0 首元素 末元素”

其实还有一个坑点,就是数列没有正数,但有0,此情况返回值必须是“0 0 0”,因为最大子列只能是若干个0组成。

下面我介绍一种较“先进”的处理方法:

首先说“只有负数和0”的处理,只要将最大子列和best的初值设为-1,那么出现0时,临时子列和为0就>best==-1,就可以更新best、first、last的值为0,0,0,如此一来“只有负数和0”的情况就和“有正数”的情况统一了。

那么“全为负数”呢?其实全为负数根本不需要额外的flag了,best==-1不就是全为负数的flag吗?只要判定当best==-1时,单独处理即可,这样一来不需要在遍历中浪费一个判定语句。

 

1.3 ★ 多种解法的统一模板(C++)★

由于PAT是IO流输入输出,故将输入转为数组,输出统一进行。并全部应用1.2中“较先进”的处理思想。

为了方便区分下标和数列元素,将下标类型都置为无符号整型 size_t。

此代码需要添加后面的各种对应解法的函数组合使用:(如果此C/C++语法都不清楚,请先学好语法)

//-----------------多种解法声明及main函数部分 记为 "解法大全.cpp"---------------
#include<vector>	//size_t是无符号整型,专用于下标,保证不会为负。
#include <stdio.h>
#include <numeric>	//使用accumulate()
using namespace std;
#define MAX_LEN 9999	//预分配能过所有样例的空间(这是恰好的!)
int Num[MAX_LEN];		//待求序列 Num[0~K-1]
//K个整数,best:最大子列和,最大子列首末元素分别为first , last。
int K, best, first, last;

//-----------------------按定义暴力解法-----------------------
//定义暴力解法 时间O(K^3),空间O(K),不记Num空间O(1)
void Brute_O_cube();
//定义累加解法 时间O(K^2),空间O(K),不记Num空间O(1)
void Brute_O_square();

//------------------------基于查表优化的解法-----------------------
//dp查表法使用变量:
int *Sum = NULL;	//Sum[i] = Num[0]+Num[1]+...+Num[i-1] ,特别的 Sum[0]= 0
size_t *i_smin=NULL;//i_smin[j]=i 等价于 min{Sum[0],Sum[1],...,Sum[j]}=Sum[i],注意其值为Sum的下标。
void init_sum();
void init_sum_min();
//查表求和解法 时间O(K^2),空间O(K)
void dp_O_square();	
//查表求和求最值 时间O(K),空间O(K)
void dp_O_linear();

//------------------------贪心思想解法--------------------
void Greedy();//贪心思想解法,一趟遍历。时间复杂度O(K),空间复杂度O(1)

int main()
{
	scanf("%d", &K);
	for (size_t i = 0; i < K; i++) {
		scanf("%d", &Num[i]);
	}	//Num[0~K-1]为待求数列
//当Num[0~K-1]为全负时,返回整个子列的首末元素,即first和last的初值。
	best = -1, first = Num[0], last = Num[K - 1];
	//若best=0 ,当Num[0~K-1]只有负数和零时,因为遇到有0不会更新 first和last ,不能正确返回 "0 0 0"。
//---------------------修改注释选用其中一种解法--------------------
	//Brute_O_cube();	//定义暴力解法 时间O(K^3),空间O(K),不记Num空间O(1)
	//Brute_O_square();	//定义累加解法 时间O(K^2),空间O(K),不记Num空间O(1)
	//dp_O_square();	//查表求和解法 时间O(K^2),空间O(K)
	//dp_O_linear();		//查表求和求最值解法 时间O(K),空间O(K)
	Greedy();			//贪心思想解法 时间O(K),空间O(K),不记Num空间O(1)
//--------------------------统一输出结果---------------------------
	printf("%d %d %d\n", max(0, best), first, last);	//注意全负时 best为初值-1。

#ifdef _DEBUGING_
	system("pause");
#endif
	return 0;
}

二、根据定义,从暴力解法到查表 (dp)线性解法

2.1 定义暴力解法 时间O(K^3),空间O(K),不记Num空间O(1)

其实只要从i,j由小到大,就可以保证找到的子列是题目所需的,在遍历中更新best、first、last的值,结束后按此输出。其中的accumulate()用于求和。

//定义暴力解法 时间O(K^3),空间O(K),不记Num空间O(1)
void Brute_O_cube() {		//best , first , last 已在main()中初始化
	for (size_t i = 0; i < K; i++) {
		for (size_t j = i; j < K; j++) {
			//now = Num[i]+Num[i+1]+...+Num[j-1]+Num[j]
			int now = accumulate(Num + i, Num + j + 1, (int)0); 
			if (now > best) {	//更优则更新。best初值为-1,即可让0来更新。
				best = now; first = Num[i]; last = Num[j];
			}
		}
	}
}

 

2.2  定义累加解法 时间O(K^2),空间O(K),不记Num空间O(1)

就是在遍历的过程中,从起点i开始累加,避免重复求和。

//定义累加解法 时间O(K^2),空间O(K),不记Num空间O(1)
void Brute_O_square() {		//best , first , last 已在main()中初始化
	for (size_t i = 0; i < K; i++) {
		int now = 0;
		for (size_t j = i; j < K; j++) {
			now += Num[j];	//累加求和
			if (now > best) {	//更优则更新。best初值为-1,即可让0来更新。
				best = now; first = Num[i]; last = Num[j];
			}
		}
	}
}

 

2.3  查表求和解法 时间O(K^2),空间O(K)

与暴力解法不同的是,将求和函数替换为查表。还有此解法外层遍历右界j,是为2.4查表求和求最值的线性解法做铺垫。根据1.1中的分析,题目要求的“子列和同大取左右端下标小者”,并不用考虑左和右哪个更优先。所以外层遍历右界,内层左界也是可以的。

void init_sum() {//前i项和制表
	if (NULL != Sum)return;
	Sum = new int[K + 1];
	Sum[0] = 0;
	for (size_t i = 0; i < K; i++) {
		Sum[i + 1] = Sum[i] + Num[i];
	}
}
//查表求和解法 时间O(K^2),空间O(K)
void dp_O_square() {//时间复杂度O(K^2);空间复杂度O(K)
	init_sum();
	for (size_t j = 0; j < K; j++) {	//为dp_O_linear()作铺垫,为统一先遍历j。
		for (size_t i = 0; i <=j; i++) {
			int now = Sum[j + 1] - Sum[i]; //数列求和(查表法)	O(K^2)
			if (now > best) {	//更优则更新。best初值为-1,即可让0来更新。
				best = now; first = Num[i]; last = Num[j];
			}
		}
	}
}

 

 2.4 ★ 查表求和求最值解法(线性时空)★

需要调用2.3的init_sum(),当然也可以独立写一个。

★这种解法具有可移植性!建议没想到的同学重点研究★

观察2.3的解法,无非就是遍历每个终点j,求出起点i<=j的所有子列中和最大者,用now = Sum[j + 1] - Sum[i];取得子列和。如果知道能使Sum[i]最小的下标i,不就可以不用遍历每个i了吗?

其实只要将Sum[0]~Sum[j]的最小值存为一个表,其中只有j一个变量,那么只要O(K)的空间复杂度就可以制成此表,且可以递推进行。

但此法想出来了,也不一定能写对!有坑点如下:

  • “最小值表”真的是存Sum[0]~Sum[j]的最小值吗?
  • Sum[]出现相同时,实际取哪个为最小值需要区分吗?
  • 如何保证最大子列的左右端下标一定是最小的呢?
  • 当数列“全为负”时,临时子列和now值会怎么取呢?

来看看我的代码是如何解决此坑点的。

//i_smin[i] = Sum[0~i]中最小值的下标,同小取下标小者
void init_sum_min() {
	if (NULL != i_smin)return;
	init_sum();	//是Sum的最大值,当然基于Sum
	i_smin = new size_t[K + 1];
	i_smin[0] = 0;		//Sum[0~0]下标只能是0
	int min = Sum[0];	//维护一个最小值
	for (size_t i = 1; i <= K; i++) {
		if (Sum[i] < min) {
			min = Sum[i];
			i_smin[i] = i;	//此情况下的i_smin[i]不能被使用。
		}
		else i_smin[i] = i_smin[i - 1];
	}
}
//查表求和求最值 时间O(K),空间O(K)
void dp_O_linear() {
	init_sum_min();	//内部自带 init_sum()
	for (size_t j = 0; j < K; ) {	//j必须递增,保证同大取下标小者。
		if (Num[j++] < 0)continue;	//★当Num[j]<0时,j不可能是最优子列的最右元素。
//注意j已经自增了1。上一语句保证了 j!=i_smin[j],保证now是代表至少1个元素的求和
		//if (++j == i_smin[j])continue;
		int now = Sum[j] - Sum[i_smin[j]]; //now=Num[i]+Num[i+1]+...+Num[j-1],i=i_smin[j]
		if (now > best) {	//更优则更新。best初值为-1,即可让0来更新。
			best = now; first = Num[i_smin[j]]; last = Num[j - 1];
		}
	}
}
  •  “最小值表”存的不是最小值,而是最小值的下标!此表与Sum的K+1的长度对应
  • 这样同小取小者下标即满足题目需求。
  • 当数列全为负时,now都不需要取了,无需更新best。
if (Num[j++] < 0)continue;    //此语句可以替换为下一语句
if (++j == i_smin[j])continue;    //注意j先增1,然后比较j == i_smin[j]

当Sum[0]~Sum[j+1]的最小值就是 Sum[j+1]时(同小取下标小者),必定用 Sum[j+1]-Sum[j]==Num[j] < 0。故第二条可以用第一条替代,但是第一条更优!因为Num[j]<0时,多加一个负数肯定不可能成为最大子列,故可以跳过now的赋值。

 

三、 ★贪心思想线性时空解法★

贪心思想是局部最优可能也是全局最优的思想。但不一定所有的求最优解的问题都能用贪心思想。

对于此数列问题,要观察是否存在贪心算法,就多手动模拟。

分析:

  • 首先最大子列两端一定不能是负数!端点若是负数,可以缩进一端取得更大子列和。
  • 因为同大取下标小者,那么左端有0可以包括,但右端有0就应排除。
  • 两个相邻非负子列如何拼接?如 N[i~j]和为非负,N[j+1~k]和为正,那么N[i~k]肯定是更优的,因为和不会变小,但左界下标小了。

基于如上分析可以尝试下面的贪心思想:

  1. 左界i从小到大遍历,只要出现 Num[i]>=0 就开始遍历右界j,尝试找出最优解。
  2. 右界j从i到大遍历,遍历求和过程中不断更新最优值记录。★当出现Sum(i,j+1)为负时,就要跳出。
  3. 重新定左界的i无需与上一循环的子列重叠(如此才能保证线性时间)

第2条∵不可能存在 k>j ,使得 Sum(i,k+1)是最优解。简单证明一下:

已知 Sum(i,j+1)<0,假设 Sum(i,k+1)是最优解 。∵ Sum(i,j+1)+Sum(j+1,k+1) = Sum(i,k+1) 

∴ Sum(j+1,k+1) = Sum(i,k+1) - Sum(i,j+1) > Sum(i,k+1)

则 Sum(j+1,k+1) 比 Sum(i,k+1) 更优!与题设矛盾!

而且当 Sum(i,j)非负而 Sum(i,j+1)为负时, Num[j]必定为负,恰好满足右端点不能为负。因为j是从小到大遍历的,所以若右端为0,不会覆盖原有最优,保证求出的最大子列同大下标取小。

第3条∵上一循环是Sum(i,j+1)<0时才跳出的。若上一循环是 Sum(i,i+1)~Sum(i,j+1),则当i<k≤ j<u,Sum(k,u)必定不是最优解。用反证法证明一下:

已知 Sum(i,j+1)<0 ① ,且对 \forall k\in [i,j],Sum(i,k)\geq 0 ②。

假设 \exists i<k \leqslant j<u 使Sum(k,u)是最优解。

则Sum(j+1,u)≤Sum(k,u),(取==时因j+1>k,所以下标取小者,仍保证假设成立)

由上 Sum(k,j+1) = Sum(k,u) - Sum(j+1,u) ≥  0 

由上  Sum(i,k) = Sum(i,j+1) - Sum(k,j+1) < 0

与前提②矛盾!

对具体问题的贪心算法的证明往往比想出来要难得多,笔者只能是粗略的解释一下,证明可能不太规范,欢迎大神指点。

代码如下:(↓需要放在 “解法大全.cpp”中执行)

//贪心思想解法,一趟遍历。时间复杂度O(K),空间复杂度O(1)
void Greedy() {		//best , first , last 已在main()中初始化
	size_t i, j;
	for (i = j = 0; j < K;) {
		//起点怎能拖后腿?遇到负数全跳过,但是0可以,因为下标i要尽量小。
		while (i < K && Num[i] < 0)i++;
		int now = 0;	//当前子列和(不能预加Num[i],否则当最大子列是仅有1个元素情况时,无法记录到best)
		for (j = i; j < K; j++) {
			if ((now += Num[j]) < 0) {	//now都负了,果断抛弃此子列
				i = j + 1; break;		//重置起点i,此时Num[j]<0,故可跳过此j。
			}
			if (now > best) {	//更优则更新。设best初值为-1,遇到0也能更新。
				best = now; first = Num[i]; last = Num[j];
			}
		}
	}
}

上面代码用了Num[]数列,实际上此算法可以做到O(1)空间复杂度,C语言代码如下:(此代码可独立运行)

//1007 Maximum Subsequence Sum (25)
//C语言 时间O(K),空间O(1)版本
#include <stdio.h>
int main()
{
	int K, best=-1, first, last,temp;
	scanf("%d\n%d", &K, &temp);
	first = temp;
	--K; do {
		while (temp < 0 && K > 0) {
			--K; scanf("%d", &temp);
		}
		int tf = temp,now = 0;
		do {
			if ((now += temp) < 0) {	//now都负了,果断抛弃此子列
				break;	//仅当N[i]<0,才可能成立,故可跳过此i。
			}
			if (now > best) {	//更优则更新。best初值为-1,即可让0来更新。
				best = now; first = tf; last = temp;
			}
		} while (K-->0 && scanf("%d", &temp));
	} while (K > 0);
	if(best<0)
		printf("0 %d %d\n", first, temp);
	else
		printf("%d %d %d\n", best, first, last);	//注意全负时 best为初值-1。
	return 0;
}

∵不能用数组表达,所以此代码比较难理解,仅作展示。

 

四、 递归分治法(线性时空)

有位师弟刚学了分治法,暴力算法到O(K^2)后就想用分治法改进。其实此题用分治法会很复杂,因为分割一刀下去很容易将最优子列切为两半,需要分左对齐、右对齐的最优子列来拼接。

笔者写的递归分治法基本思想如下:

  • 递归分治代入的参数(begin end)分别表示数列的最左元素下标最右元素下标+1
  • 子列长度为1时为递归出口,直接处理。否则按二等分(如何取整没有关系)均分(分别记为LH和RH),因本子列长度至少为2,均分保证子节点的子列长度至少为1。
  • 需要返回本子列的全部元素和以及本子列的“左端靠最左”时的最优子列(记为L子列)和“右端靠最右”时的最优子列(记为R子列),用于父节点进行拼接和判断。
  • 而递归中的本子列中的最优子列,不需要返回给递归父节点,直接和全局最优的记录比较即可。

与全局最优比较时,若子列和相同必须比较左右界下标,除非是选择性分割,如此一来不过是用递归包装的贪心算法。可能是笔者水平有限,无法像上面各类算法那样做到按序遍历下标。

实际上此解法的执行速度与贪心算法相差无几。

代码如下(单独cpp文件)

//------------------------递归分治法.cpp-------------------------
#include<vector>	//size_t是无符号整型,专用于下标,保证不会为负。
#include <stdio.h>
using namespace std;
#define MAX_LEN 9999	//预分配能过所有样例的空间(这是恰好的!)
int Num[MAX_LEN];		//待求序列 Num[0~K-1]
int K;	//Num数列长度为K
//#define _SHOW_
//#define _STATISTIC_
struct SUBSEQUENCE {	//子列信息
	//sum:子列和(== -1 保证只有负数和0的情况可以更新为0)
	int sum = -1;
	//begin==子列的首元素下标; end-1==子列末元素下标
	size_t begin = 0, end = 0;	//避免遇到-1时进行无谓地更新,故取小者0,0
	SUBSEQUENCE() {}
	inline void reNew(int _sum, size_t _begin, size_t _end) {
		if (_sum < sum)return;
		if (_sum > sum) sum = _sum;
		else if (_begin>begin || _end>end) return;
		begin = _begin; end = _end;
	}
}best;	//记录全局最优

struct INFO {	//DAC专用返回结果的结构体
	int sum = 0;	//整个子列的和,可以为负
//L,R分别是边界靠左、边界靠右的最优子列。若不可能拼接成更优子列,则设子列为“零子列”即和为0且长度为0。
	int Lmax = 0;	//子列和为正,否则为“零子列”
	int Rmax = 0;	//子列和为非负,否则为“零子列”
	size_t L_end, R_begin;	//L,R分别为零子列时:L_end=begin、R_begin=end。
	INFO(int sum) :sum(sum) {
#ifdef _STATISTIC_
		Construct_times++;
#endif // _STATISTIC_
	}
	static unsigned int Construct_times;	//用于记录构造INFO对象的次数
};
unsigned int INFO::Construct_times = 0;

INFO DAC(size_t begin, size_t end) {
	if (begin + 1 == end) {
		INFO res(Num[begin]);
		if (Num[begin] < 0) {	//整个子列(就1个元素)都是负,故内部所有子列和都是0子列
			res.L_end = begin; res.R_begin = end;
		}else {	//非负
			best.reNew(Num[begin], begin, end);	//单个元素也要更新最大子列
			res.Lmax = res.Rmax = Num[begin];
			if (Num[begin] == 0) {
				res.L_end = res.R_begin = begin;
			}else {
				res.L_end = end; res.R_begin = begin;
			}
		}
		return res;
	}
	size_t mid = (begin + end) / 2;
	INFO LH = DAC(begin, mid), RH = DAC(mid, end);	//顺序不可对调,必须先左后右保证同大下标取小
	if (LH.R_begin<mid && RH.L_end>mid) {	//左右分子列靠中间边界的最优子列都不是“零子列”。
		best.reNew(LH.Rmax + RH.Lmax, LH.R_begin, RH.L_end);	//可能拼接的子列更优。
	}
	INFO res(LH.sum + RH.sum);	//要返回本子列和,及靠左右边界最优子列信息。
	if (LH.sum + RH.Lmax > LH.Lmax) {	//【整个L + RH.L】更大。
		res.Lmax = LH.sum + RH.Lmax;
		res.L_end = RH.L_end;
	}
	else {		//【LH.L】更大或同大时,取下标小者故只取左。
		res.Lmax = LH.Lmax;
		res.L_end = LH.L_end;
	}
	if (RH.sum + LH.Rmax >= RH.Rmax) {	//【LH.R + R】更大或相等。
		res.Rmax = RH.sum + LH.Rmax;
		res.R_begin = LH.R_begin;
	}
	else {		//【RH.R】更大。
		res.Rmax = RH.Rmax;
		res.R_begin = RH.R_begin;
	}
#ifdef _SHOW_
	cout << "i=" << begin << " j=" << end - 1 << "\tsum=" << res.sum
		<< "\tLmax=" << res.Lmax << " Le=" << res.L_end
		<< "\tRmax=" << res.Rmax << " Rb=" << res.R_begin
		<< "\n\t" << Num[begin];
	for (size_t i = begin; ++i < end;)cout << "," << Num[i];
	cout << endl;
#endif // _SHOW_
	return res;
}

int main() {
	scanf("%d", &K);
	for (size_t i = 0; i < K; i++) {
		scanf("%d", &Num[i]);
	}	//Num[0~K-1]为待求数列
	DAC(0, K);
	if (best.sum<0)
		printf("0 %d %d\n", Num[0], Num[K - 1]);
	else
		printf("%d %d %d\n", best.sum, Num[best.begin], Num[best.end - 1]);
#ifdef _STATISTIC_
	cout << INFO::Construct_times << endl;	//Construct_times==2*K-1
	system("pause");
#endif // _STATISTIC_
	return 0;
}
  • 写之前笔者以为分治法只能写成O(K·logK)的时间复杂度,而事实上本递归算法是线性时空的!
  • 首先分治法必须依赖已存储的数组来表示数列和压栈递归,故空间复杂度为O(K+logK)=O(K)。
  • 此递归算法函数内无与子数列长度相关的循环语句,所以每个递归节点的时间复杂度都是O(1)。
  • 实际上执行此算法调用递归函数不超过2*K次,故时间复杂度为 O(K*2)=O(K)。不信可以解开“//#define _STATISTIC_”的注释,得到构造INFO对象的次数,也就是递归函数返回值的次数。

笔者水平有限,写出来的基于分治思想的递归解法C++版本即便去掉所有注释,也要七八十行,结构比较复杂。如果高人指点一二,不胜感激。

后记

此题笔者相隔一年做了两次,第一次是按第二节的解法优化的,当时做到O(K^2)就AC了,而后继续优化为时空O(K)的查表解法。第二次做是因为师弟的提问,直接上了贪心算法解法,不到20分钟只WA了一次就AC,不知道是不是我曾经见过此题的贪心算法。此问题并不适合采用分治法,虽然是线性时空,但代码实在复杂。所有代码都是本人写的,如有相似,纯属英雄所见略同。

欢迎各位发表合理评价和提意见。

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值