程序员面试精粹02

求子数组最大和的程序


题目:输入一个整型数组,数组里有正数也有负数。数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。

            求所有子数组的和的最大值。要求时间复杂度为O(n)。


            举例来说:输入的数组为 1, -2, 3, 10, -4, 7, 2, -5,和最大的子数组为3, 10, -4, 7, 2,因此输出为该子数组的和18。

             ==================  1, -2, 3, 10, -4, 7,2, -5,和最大的子数组为3, 10, -4, 7, 2    =====================  


一.题目分析


    我们先不管算法神马的,先从最一般的的思考方式去分析这道问题。一串整数数组,有正有负,如何从中选出和最大的子数组呢?

    1.这串子数组一定是从一个正数开始;

    2.它一定也以一个正数作为最后一个元素结尾。

   

    为什么呢?举例来说:1, -2, 3, 10, -4, 7, 2, -5 。如果一个子数组以一个负数开始,那么这个子数组一定小于这个子数组中以正数开头的子数组... ...晕了是吧?

    这里我们用反证法来证明一下~

   

    假设一个数组的最大子数组是以一个负数开头的,即A = { -2, 3, 10, -4, 7, 2 }是最大的子数组,那么A的子数组B = { 3, 10, -4, 7, 2 }的和是一定大于A的。因为A = -2 + B;所以

B > A,而我们假设A是最大的子数组。显然结论与我们假设的相矛盾(B竟然更大~)。由此我们知道这串子数组一定是从一个正数开始。同理我们就可以得出这个子数组一定也以一个正数作为最后一个元素结尾的。


    那么这个结论给我们什么启示?难不成要让我们去找到所有的以正数开头结尾的子数组,比较他们的和?当然可以,只不过O(n)这个题设要求就不满足了。为什么呢,还是看看这个例子:1, -2, 3, 10, -4, 7, 2, -5

我们首先从头开始遍历:我们首先找到第一个正数,也就是1。

====1, -2, 3

====1, -2, 3, 10

====1, -2, 3, 10, -4, 7

====1, -2, 3, 10, -4, 7, 2

这是第一遍,总共经过比较了n个数,

那么第二遍我们继续向前找以正数开始,正数结尾的子数组... ...以此类推。你会发现最坏的情况下要有n*(n - 1)* ... *1 即 n!次才能全部找到(此时原数组全是正数)。最乐观的情况是{  1,-1, -1, ...  ,-1  }这种情况只要遍历一次就可以了,但前提是你得有一个记录的标记来告诉你有n个数,(n - 1)个负数才行。显然这种情况只是理想情况。我们在分析为题的时候要以一般情况来分析。算法设计分析中美其名曰:平均时间复杂度~


    所以这里我们就引出了一个叫做" 贪心算法 "的东西。所以大家想要扩展知识的话可以去学习一下" 贪心算法 "的相关知识。为什么这道问题要用贪心算法来解决呢?因为许多可以用贪心算法求解的问题中看到这类问题一般具有2个重要的性质:贪心选择性质最优子结构性质

    擦~什么意思... ...下面我们先用书本方法解释一下

1.贪心选择性质

    所谓贪心选择性质是指所求问题的整体最优解可以通过一系列局部最优的选择,换句话说,当考虑做何种选择的时候,我们只考虑对当前问题最佳的选择而不考虑子问题的结果。这是贪心算法可行的第一个基本要素。贪心算法以迭代的方式作出相继的贪心选择,每作一次贪心选择就将所求问题简化为规模更小的子问题。
    对于一个具体问题,要确定它是否具有贪心选择性质,必须证明每一步所作的贪心选择最终导致问题的整体最优解。

2.最优子结构性质

    当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。问题的最优子结构性质是该问题可用贪心算法求解的关键特征。

    贪心算法的基本思路如下:

    1.建立数学模型来描述问题。

    2.把求解的问题分成若干个子问题。

    3.对每一子问题求解,得到子问题的局部最优解。

    4.把子问题的解局部最优解合成原来解问题的一个解。


    所以,根据上面的介绍我们想找到和最大子数组,那就从头开始找这个子数组。我们就从第一个数开始( 1, -2, 3, 10, -4, 7, 2, -5 ) ,此时要假设子数组的和sum开始为0,并且我们能确保最后的子数组一定大于0(因为这个数组中有正有负,大不了最后的子数组就是一个数,他是整个数组中的最大正数罢了)。所以当前面的和相加小于0的时候,立刻抛弃。因为根据贪心选择性,我们认为如果前面的和小于0,那还不如跳过它重新开始。想想这里的证明是不是和前面的那个一定要以正数开头的证明是一样的呢,所以前面的那个证明正式一种贪心选择性的表现。如果前面的和大于0,假设新加进来的数大于0,那么新组成的组就是更新后的最优的解!!

    如果还是有点思路混乱,让我们在代码中继续深入理解~

    程序如下:

int max_sub_array(int arr[], int size)
{
	int i 	= 0;			//一个记录原数组的游标
	int sum = 0;			//sum来记录前k个数的和(k < n)
	int max = -(1 << 30);		//max记录我们找到的最大和,开始我们设置为-2^31
	
	//循环到size位置,我们可以寻找子数组的子数组,即假设 { 1,-2, 3, 10, -4, 7, 2 }
	//当我们设size = 4,那么其实测试的数组就是{ 1, -2, 3, 10 }。
	while ( i < size )			
	{
		//这就是我们前面提到的,sum一直向后加arr[i],当sum < 0时,我们抛弃sum,就是跳过前k个数,因为
		//前k个数不是最优的子结构,我们就将sum置0,从新开始。
		sum += arr[i++];
		
		if ( sum < 0 )
		{
			sum = 0;
		}
		
		//当sum加上一个正数的时候(sum新) > (sum原),而max就是(sum原)。于是我们更新max。
		//当sum加上一个负数的时候(sum新) < (sum原),max不更新。这就是为什么最后一个数
		//一定是大于0的了
		else if ( sum > max )
		{
			max = sum;
		}

	}
	
	return max;
}

测试函数如下:

int main()
{
	int arr[] = {-1,3,5,-2, 7, 10, 3, -1};
	int a = 0;
	int b = 0;
	
	a = max_sub_array(arr, 5);
	b = max_sub_array(arr, 8);
	
	printf( "%d\n", a );
	printf( "%d\n", b );
	
	return 0;
}


测试结果如下:


在理解的前提下,自己动手试试~


二.就这些么?


    这就完事了么?是的。对于题目已经完事了~但是我们还可以想得更多!

    请问,我们找到了最大的和max,但是这个子数组在哪里?A = { 1, -2, 3, 10, -4, 7, 2, -5} ,的子数组是从哪里开始的?她的起始位置在哪。

   

    为了解决这个问题我们,我们依然是跟着代码来看看它都做了什么。

int max_starter(int arr[], int count)
{
	//发现这3个和之前没啥区别
	int i = 0;
	int max = -(1 << 30);
	int sum = 0;
	
	//那这两个又是什么意思呢?
	//starter_final:是最大子组起始位置;额... 那个starter呢?
	//这个starter是记录临时的起始值,说的挺别扭的,要不看看例子? 对于int arr[] = {-1,3,5,-12, 7, 10, 3, -1}
	//第一个starter是2,即arr[1] = 3那里,因为(arr[0] = -1) < 0,则sum < 0,直接就跳到arr[1]那里从新开始了,所	
	//以我们看看这个starter都有哪些? arr = 3, arr = 7这里,是吧?自己动手算算。也就是说starter记录着所有可能的
	//开始位置,而这个starter_final通过比较记录着存在和最大的子数组的起始位置。
	int starter = 0;
	int starter_final = 0;
	
	while ( i < count )
	{
		sum += arr[i++];
		if ( sum < 0 )
		{
			starter = i;
			sum = 0;
		}	
		else if ( sum > max )
		{
			max = sum;
			starter_final = starter;
		}		
	}
	
	//我们返回的是位置,需要加1,即arr[4] = 7,的位置在数组的第5位。
	return (starter_final + 1);

}

测试结果如下:


在理解的前提下,自己动手试试~


三.More, I need more power(鬼泣DMC中,堕落维吉尔的台词)


    是的,我们还可以做的更多!

    现在知道了和最大的子数组了,又知道了子数组在原数组的起始位置。那么这个子数组长的什么样呢,我们能不能知道她就是 { 3, 10, -4, 7, 2 }呢。

   

    其实很简单啦,我们知道了数组的和,和起始位置。那么用总和减去起始位置那个元素,即 18 - 3 = 15,同时将那个指向起始位置的标志向后移一下,再用一个count来记录子数组有多少个元素。又是说的好乱,所以依然是例子来说话:这里 max = 18,count = 0是吧,

max = max - 3 = 15,count++ (所以count = 1了)

max = max - 10 = 5,count++  (count = 2)

max = max - (-4) = 9,count++  (count = 3)

max = max - 7 = 2,count++  (count = 4)

max = max - 2 = 0,count++  (count = 5)


    至此count计算完毕,共5个数在这个子数组里。知道了子数组在原数组中的位置,又知道了她的长度~那么就能确定她到底是什么样的了,不是么?

    我不说话,就看看代码~

//子组长度
int max_sub_array_length(int arr[], int count)
{
	int max_value	= 0;
	int starter 	= 0;
	int length 		= 0;
	
	max_value 	= max_sub_array(arr, count);
	starter 	= max_starter(arr, count) - 1;
	
	while (max_value > 0)
	{
		max_value -= arr[starter++];
		++length;
	}
	
	return length;
}

    至于怎么把它打印出来就交给大家来完成了,动手试试!!!

    最后把全部示例代码列出来:

/*************************************************
 *
 *作者:钟凌霄
 *时间:2014.1.6
 *题目:求子数组的最大和问题
 *
 *************************************************/

#include <stdlib.h>
#include <stdio.h>

//*******************子数组最大和**************************
int max_sub_array(int arr[], int size)
{
	int i 	= 0;			//一个记录原数组的游标
	int sum = 0;			//sum来记录前k个数的和(k < n)
	int max = -(1 << 30);		//max记录我们找到的最大和,开始我们设置为-2^31
	
	//循环到size位置,我们可以寻找子数组的子数组,即假设 { 1,-2, 3, 10, -4, 7, 2 }
	//当我们设size = 4,那么其实测试的数组就是{ 1, -2, 3, 10 }。
	while ( i < size )			
	{
		//这就是我们前面提到的,sum一直向后加arr[i],当sum < 0时,我们抛弃sum,就是跳过前k个数,因为
		//前k个数不是最优的子结构,我们就将sum置0,从新开始。
		sum += arr[i++];
		
		if ( sum < 0 )
		{
			sum = 0;
		}
		
		//当sum加上一个正数的时候(sum新) > (sum原),而max就是(sum原)。于是我们更新max。
		//当sum加上一个负数的时候(sum新) < (sum原),max不更新。这就是为什么最后一个数
		//一定是大于0的了
		else if ( sum > max )
		{
			max = sum;
		}
	}
	
	return max;
}


//*******************子数组起始位置*************************
int max_starter(int arr[], int count)
{
	//发现这3个和之前没啥区别
	int i 	= 0;
	int max = -(1 << 30);
	int sum = 0;
	
	//那这两个又是什么意思呢?
	//starter_final:是最大子组起始位置;额... 那个starter呢?
	//这个starter是记录临时的起始值,说的挺别扭的,要不看看例子? 对于int arr[] = {-1,3,5,-12, 7, 10, 3, -1}
	//第一个starter是2,即arr[1] = 3那里,因为(arr[0] = -1) < 0,则sum < 0,直接就跳到arr[1]那里从新开始了,所	
	//以我们看看这个starter都有哪些? a = 3, a = 7这两部分,是吧?自己动手算算。也就是说starter记录着所有可能的
	//开始位置,而这个starter_final通过比较记录着存在和最大的子数组的起始位置。
	int starter = 0;
	int starter_final = 0;
	
	while ( i < count )
	{
		sum += arr[i++];
		if ( sum < 0 )
		{
			starter = i;
			sum = 0;
		}	
		else if ( sum > max )
		{
			max = sum;
			starter_final = starter;
		}		
	}
	
	//我们返回的是位置,需要加1,即arr[4] = 7,的位置在数组的第5位。
	return (starter_final + 1);

}


//*********************子数组长度************************
int max_sub_array_length(int arr[], int count)
{
	int max_value	= 0;
	int starter 	= 0;
	int length 		= 0;
	
	max_value 	= max_sub_array(arr, count);
	starter 	= max_starter(arr, count) - 1;
	
	while (max_value > 0)
	{
		max_value -= arr[starter++];
		++length;
	}
	return length;
}


//**********************全部测试用例***********************
int main()
{
	int arr[] = {-1,3,5,-12, 7, 10, 3, -1};
	int a = 0;
	int b = 0;
	int c = 0;
	
	a = max_sub_array(arr, 6);
	b = max_starter(arr, 8);
	c = max_sub_array_length(arr, 5);

	printf( "子数组最大和:%d\n", a );
	printf( "子数组起始位置:%d\n", b );
	printf( "子数组长度:%d\n", c );
	
	return 0;
}

    这里我们并没有将该代码完善化,如果要问的话,那当然是留给大家的作业了~

    当我们取a = 1的时候是以个负数,那么该程序就会告诉你最大和是 -1073741824(哪来的?怎么解决)。还有一些问题就让大家去思考发现,并独立去解决了~恩,加油!  


    至此我们已经基本上把这道题弄明白了,如果有什么问题的话欢迎指正和留言。   



【基于QT的调色板】是一个使用Qt框架开发的色彩选择工具,类似于Windows操作系统中常见的颜色选取器。Qt是一个跨平台的应用程序开发框架,广泛应用于桌面、移动和嵌入式设备,支持C++和QML语言。这个调色板功能提供了横竖两种渐变模式,用户可以方便地选取所需的颜色值。 在Qt中,调色板(QPalette)是一个关键的类,用于管理应用程序的视觉样式。QPalette包含了一系列的颜色角色,如背景色、前景色、文本色、高亮色等,这些颜色可以根据用户的系统设置或应用程序的需求进行定制。通过自定义QPalette,开发者可以创建具有独特视觉风格的应用程序。 该调色板功能可能使用了QColorDialog,这是一个标准的Qt对话框,允许用户选择颜色。QColorDialog提供了一种简单的方式来获取用户的颜色选择,通常包括一个调色板界面,用户可以通过滑动或点击来选择RGB、HSV或其他色彩模型中的颜色。 横渐变取色可能通过QGradient实现,QGradient允许开发者创建线性或径向的色彩渐变。线性渐变(QLinearGradient)沿直线从一个点到另一个点过渡颜色,而径向渐变(QRadialGradient)则以圆心为中心向外扩散颜色。在调色板中,用户可能可以通过滑动条或鼠标拖动来改变渐变的位置,从而选取不同位置的颜色。 竖渐变取色则可能是通过调整QGradient的方向来实现的,将原本水平的渐变方向改为垂直。这种设计可以提供另一种方式来探索颜色空间,使得选取颜色更为直观和便捷。 在【colorpanelhsb】这个文件名中,我们可以推测这是与HSB(色相、饱和度、亮度)色彩模型相关的代码或资源。HSB模型是另一种常见且直观的颜色表示方式,与RGB或CMYK模型不同,它以人的感知为基础,更容易理解。在这个调色板中,用户可能可以通过调整H、S、B三个参数来选取所需的颜色。 基于QT的调色板是一个利用Qt框架和其提供的色彩管理工具,如QPalette、QColorDialog、QGradient等,构建的交互式颜色选择组件。它不仅提供了横竖渐变的色彩选取方式,还可能支持HSB色彩模型,使得用户在开发图形用户界面时能更加灵活和精准地控制色彩。
标题基于Spring Boot的二手物品交易网站系统研究AI更换标题第1章引言阐述基于Spring Boot开发二手物品交易网站的研究背景、意义、现状及本文方法与创新点。1.1研究背景与意义介绍二手物品交易的市场需求和Spring Boot技术的适用性。1.2国内外研究现状概述当前二手物品交易网站的发展现状和趋势。1.3论文方法与创新点说明本文采用的研究方法和在系统设计中的创新之处。第2章相关理论与技术介绍开发二手物品交易网站所涉及的相关理论和关键技术。2.1Spring Boot框架解释Spring Boot的核心概念和主要特性。2.2数据库技术讨论适用的数据库技术及其在系统中的角色。2.3前端技术阐述与后端配合的前端技术及其在系统中的应用。第3章系统需求分析详细分析二手物品交易网站系统的功能需求和性能需求。3.1功能需求列举系统应实现的主要功能模块。3.2性能需求明确系统应满足的性能指标和安全性要求。第4章系统设计与实现具体描述基于Spring Boot的二手物品交易网站系统的设计和实现过程。4.1系统架构设计给出系统的整体架构设计和各模块间的交互方式。4.2数据库设计详细阐述数据库的结构设计和数据操作流程。4.3界面设计与实现介绍系统的界面设计和用户交互的实现细节。第5章系统测试与优化说明对系统进行测试的方法和性能优化的措施。5.1测试方法与步骤测试环境的搭建、测试数据的准备及测试流程。5.2测试结果分析对测试结果进行详细分析,验证系统是否满足需求。5.3性能优化措施提出针对系统性能瓶颈的优化建议和实施方案。第6章结论与展望总结研究成果,并展望未来可能的研究方向和改进空间。6.1研究结论概括本文基于Spring Boot开发二手物品交易网站的主要发现和成果。6.2展望与改进讨论未来可能的系统改进方向和新的功能拓展。
1. 用户与权限管理模块 角色管理: 学生:查看个人住宿信息、提交报修申请、查看卫生检查结果、请假外出登记 宿管人员:分配宿舍床位、处理报修申请、记录卫生检查结果、登记晚归情况 管理员:维护楼栋与房间信息、管理用户账号、统计住宿数据、发布宿舍通知 用户操作: 登录认证:对接学校统一身份认证(模拟实现,用学号 / 工号作为账号),支持密码重置 信息管理:学生完善个人信息(院系、专业、联系电话),管理员维护所有用户信息 权限控制:不同角色仅可见对应功能(如学生无法修改床位分配信息) 2. 宿舍信息管理模块 楼栋与房间管理: 楼栋信息:名称(如 "1 号宿舍楼")、层数、性别限制(男 / 女 / 混合)、管理员(宿管) 房间信息:房间号(如 "101")、户型(4 人间 / 6 人间)、床位数量、已住人数、可用状态 设施信息:记录房间内设施(如空调、热水器、桌椅)的配置与完好状态 床位管理: 床位编号:为每个床位设置唯一编号(如 "101-1" 表示 101 房间 1 号床) 状态标记:标记床位为 "空闲 / 已分配 / 维修中",支持批量查询空闲床位 历史记录:保存床位的分配变更记录(如从学生 A 调换到学生 B 的时间与原因) 3. 住宿分配与调整模块 住宿分配: 新生分配:管理员导入新生名单后,宿管可按专业集中、性别匹配等规则批量分配床位 手动分配:针对转专业、复学学生,宿管手动指定空闲床位并记录分配时间 分配结果公示:学生登录后可查看自己的宿舍信息(楼栋、房间号、床位号、室友列表) 调整管理: 调宿申请:学生提交调宿原因(如室友矛盾、身体原因),选择意向宿舍(需有空位) 审批流程:宿管审核申请,通过后执行床位调换,更新双方住宿信息 换宿记录:保存调宿历史(申请人、原床位、新床位、审批人、时间) 4. 报修与安全管理模块 报修管理: 报修提交:学生选择宿舍、设施类型(如 "
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值