上一篇解答了在栈里面求最小值元素的问题,这一篇,来聊聊怎么找到数组中子数组的最大和。
通过这道题,你可以掌握
- 如何根据用户输入创建数组
- 如何在一连串数字中找到和最大的某一段连续数字子串
- 如何发现问题的潜在规律并利用这个规律设计算法,解决问题
思路
- 连续数相加要最大,说明左右两边的数肯定不是负数,否则不可能最大
- 连续数序列中允许存在负数,前提是负数前面的一段正数相加要大于这个负数,否则两者抵消后,和会变小
算法
遍历数组
遇到正数,不断累加,遇到的第一个正数要记录下标
遇到负数,记录下标,把此下标减1和之前记录的正数的下标之间的数组作为一个可能最大数组,
与之前的可能最大数组比较,若变大,则取代!
判断累加的值与负数大小关系
如果累加值大于负数,则继续累加
如果累加值小于等于负数,舍弃此负数,向前移动,累加值清零
源代码
<span style="font-size:14px;">#include <stdio.h> #include<stdlib.h> #include <iostream> #include<vector> #include<sstream> using namespace std; /** 输入一个整形数组,数组里有正数也有负数。 数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。 求所有子数组的和的最大值。要求时间复杂度为 O(n)。 例如输入的数组为 1, -2, 3, 10, -4, 7, 2, -5,和最大的子数组为 3, 10, -4, 7, 2, 因此输出为该子数组的和 18。 思路 连续数相加要最大,说明左右两边的数肯定不是负数 连续数序列中允许存在负数,前提是负数前面的一段正数相加要大于这个负数 算法: 遍历数组 遇到正数,不断累加,遇到的第一个正数要记录下标 遇到负数,记录下标, 把此下标减1和之前记录的正数的下标之间的数组作为一个可能最大数组, 与之前的可能最大数组比较,若变大,则取代! 判断累加的值与负数大小关系 如果累加值大于负数,则继续累加 如果累加值小于等于负数,舍弃此负数,向前移动,累加值清零 */ void main() { //根据用户输入创建数组 vector <int> oriArray; int n=0,count=0; string str; bool endFlag=true; while(endFlag){ cout<<"请输入第"<<count<<"个数组元素,输入e结束输入"<<endl; cin>>str; if(str=="e"){ endFlag=false; }else{ stringstream(str)>> n; oriArray.push_back(n); count++; } } cout<<"所输入的数组为"<<endl; for(int i =0;i<oriArray.size();i++){ cout<<oriArray[i]<<" "; } //求最大子数组 /** add:累加值 ori:累加值的起始角标 max:最大和 maxOri:最大和子数组起始角标 maxEnd:最大和子数组结尾角标 */ int add=0,ori=0,max=0,maxOri=0,maxEnd=0; bool firstPos=true; //遍历 for(int i =0;i<oriArray.size();i++){ //遇到正数 if(oriArray[i]>=0){ add+=oriArray[i];//不断累加 if(firstPos){//遇到的第一个正数要记录下标 ori=i; firstPos=false; } }else{ //遇到负数 //之与前的可能最大和比较,若变大,则取代! if(add>max){ max=add; maxOri=ori; maxEnd=i-1; } /** 判断累加的值与负数大小关系 如果累加值大于负数,则继续累加 如果累加值小于等于负数,舍弃此负数,向前移动,累加值清零 */ if(oriArray[i]+add>0){ add+=oriArray[i]; }else{ add=0; if(i+1<oriArray.size()) ori=i+1; } } } //跳出循环后再判断一次 if(add>max){ max=add; maxOri=ori; maxEnd=oriArray.size()-1; } cout<<endl; cout<<maxOri<<" "<<maxEnd<<endl; cout<<"最大子数组的最大值为"<<max<<endl; cout<<"最大子数组为"<<endl; for(int i=maxOri;i>=maxOri&&i<=maxEnd;i++){ cout<<oriArray[i]<<" "; } system("pause"); }</span>
运行图
此题的关键在于发现最大和子数组的两端不能是负数这个规律。
做完之后在网上找了找类似的题目答案,发现有大神给出了更牛的解法,在此共享一下
思路2:
当前面的几个数,加起来后,b<0后,
把 b 重新赋值,置为下一个元素,b=a[i]。
当 b>sum,则更新 sum=b;
若 b<sum,则 sum 保持原值,不更新
源代码2:
<span style="font-size:14px;">#include <stdio.h> #include<stdlib.h> #include <iostream> #include<sstream> using namespace std; /** 输入一个整形数组,数组里有正数也有负数。 数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。 求所有子数组的和的最大值。要求时间复杂度为 O(n)。 例如输入的数组为 1, -2, 3, 10, -4, 7, 2, -5,和最大的子数组为 3, 10, -4, 7, 2, 因此输出为该子数组的和 18。 思路 当前面的几个数,加起来后,b<0后, 把 b 重新赋值,置为下一个元素,b=a[i]。 当 b>sum,则更新 sum=b; 若 b<sum,则 sum 保持原值,不更新 */ int maxSum(int* a, int n) { int sum=0; int b=0; for(int i=0; i<n; i++) { if(b<=0) //前面的几个数,加起来后,b<0 b=a[i];//b 重新赋值 else b+=a[i];//前面的几个数,加起来后,b>=0,继续累加 if(sum<b) sum=b;// b>sum,则更新 sum=b } return sum; } void main() { int a[10]={1,-8,6,3,-1,5,7,-2,0,1}; cout<<"子数组的最大和为"<<maxSum(a,10)<<endl; system("pause"); }</span>
解法二之所以比解法一简练,在于他不仅仅意识到两端的数不能为负数,而且只要那一串的子数组相加小于0,就不可能是最大和子数组的一部分。
每个问题都有其发生的规律,设计算法的过程就是发现规律并加以利用的过程。
就好比打羽毛球,如果我发现只要我一回高远球,对手就放短球,那么我下次回完高远就直接冲到网前准备扑杀。
时间: 2024-12-27 07:35:30