给定一个整数数组,找到一个具有最大和的子数组,返回其最大和。
样例
给出数组[−2,2,−3,4,−1,2,1,−5,3],符合要求的子数组为[4,−1,2,1],其最大和为6
方法一:暴力枚举,时间复杂度O(n3)
1、找出子数组的最左端点 for i<-1 to n
2、找出子数组的最右端点 for j<-i to n
3、求和,找出最大值 sum = a[i] +……+a[j]; ans = max(ans, sum)
class Solution {
public:
/**
* @param nums: A list of integers
* @return: A integer indicate the sum of max subarray
*/
int maxSubArray(vector<int> nums) {
// write your code here
int n = nums.size();
int ans = -1000000; //因为求最大值,所以给结果初始化一个很小的数
for(int i=0; i<n; i++)
{
for(int j=i; j<n; j++)
{
int sum = 0;
for(int k=i; k<=j; k++)
{
sum += nums[k];
}
if(sum > ans)
{
ans = sum;
}
}
}
return ans;
}
};
方法二:优化枚举,时间复杂度O(n2)
重复利用已计算的子数组和,
左端点相同的子数组,随着长度的增加,排在前面的元素被不断重复相加。如果我们记录下之前的求和结果,
就不需要对前面的元素再进行计算,比方法一少了一重循环。
class Solution {
public:
/**
* @param nums: A list of integers
* @return: A integer indicate the sum of max subarray
*/
int maxSubArray(vector<int> nums) {
// write your code here
int n = nums.size();
int ans = -1000000;
for(int i=0; i<n; i++)
{
int sum = 0;
for(int j=i; j<n; j++)
{
sum += nums[j];
if(sum > ans)
{
ans = sum;
}
}
}
return ans;
}
};
方法三:动态规划,时间复杂度O(n)
首先来说下动态规划法的概念。
动态规划求解的基本步骤:
能采用动态规划求解的问题一般要具有3个性质:
(1)最优化原理:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。
(2)无后效性:即某阶段的状态一定确立,就不受这个状态以后决策的影响,也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。
(3)有重叠子问题:即子问题之间是不独立的,一个字问题在下一阶段的决策中可能被多次使用到(该性质不是动态规划的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势)
动态规划算法有一定自己的模式,一般要经历如下几个步骤:
(1)划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段,在划分阶段注重划分后的阶段一定要是有序或者是可排序的,否则问题就无法求解。
(2)确定状态和状态变量:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。当然,状态的选择要满足无后效性。
(3)确定决策并写出状态转移方程:因为决策和状态转移方程有着天然的联系。状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。所以决定了决策,状态转移方程也就可以写出。
但事实上常常是反过来做,根据相邻的两个阶段的状态之间的关系来确定决策方法和转移方程。
(4)寻找边界条件:给出的状态转移方程是一个递推式,需要一个递推的终止条件或者边界条件。
动态规划法的基本思想:
将待求解的基本问题分为若干个子问题,按照顺序求解子阶段,前一个问题的解,为后一子问题的求解提供有用的信息。在求解任何问题时,列出各种可能的局部解,通过决策保留那些可能达到最优的局部解,丢弃其他局部解。依次解决各个子问题,最后一个子问题就是初始化的解。
动态规划采用的基本办法:
为了节约重复求相同子问题的时间,引入一个数组或者一组变量,不管它们是否对最终解有用,把所有子问题的解存于该数组或者这组变量中。
/*如果用函数f(i)表示以第i个数字结尾的子数组的最大和,那么我们需要求出max(f[0...n])。
我们可以给出如下递归公式求f(i)
|-- array[i] 如果i==0或者f(i-1)<0
f(i)=|
|-- f(i-1) + array[i] 如果f(i-1)>0
这个公式的意义:
当以第(i-1)个数字为结尾的子数组中所有数字的和f(i-1)小于0时,如果把这个负数和第i个数相加,得到的结果反而不第i个数本身还要小,所以这种情况下最大子数组和是第i个数本身。
如果以第(i-1)个数字为结尾的子数组中所有数字的和f(i-1)大于0,与第i个数累加就得到了以第i个数结尾的子数组中所有数字的和。
*/
public class array {
public static void main(String[] args){
int[] array = {-9,1,3,5,-1,7,-5,3,1};
int len=array.length;
int[] c=new int[len];//引入一个数组
int max = -1000;//用来记录数组c[]中的最大值
int start = 0;//记录数组中子数组的最大和的开始位置
int end = 0;//记录数组中子数组的最大和的结束位置
int tmp = 0;//中间变量
c[0] = array[0];
for (int i = 1; i < len; ++i)
{
if (c[i - 1] > 0)
{
c[i] = c[i - 1] + array[i];
}
else
{
c[i] = array[i];
tmp = i;
}
if (c[i] > max)
{
max = c[i];
start = tmp;
end = i;
}
}
System.out.println(start+"~"+end+"Max is:"+max);
}
}
2.针对这个问题,递推公式是DP[i] = max{DP[i-1] + A[i],A[i]};既然转移方程出来了,意味着写一层循环就可以解决这个问题。
如果子串A的和是负数,而子串B包含子串A,那B则不需要进行计算。这样就省去了一些计算步骤。
将子串和为负数的子串丢掉,只留和为正的子串。
class Solution {
public:
/**
* @param nums: A list of integers
* @return: A integer indicate the sum of max subarray
*/
int maxSubArray(vector<int> nums) {
// write your code here
int n = nums.size();
int ans = -1000000;
int sum = 0;
for(int i=0; i<n; i++)
{
sum += nums[i];
if(sum > ans)
{
ans = sum;
}
if(sum < 0)
{
sum = 0; //子串和为负数,丢掉
}
}
return ans;
}
};