比如對於數組[1,-2,3,5,-1,2] 最大子數組和是sum[3,5,-1,2] = 9, 我們要求函數輸出子數組和的最大值,並且返回子數組的左右邊界(下面函數的left和right參數).
本文我們規定當數組中所有數都小於0時,返回數組中最大的數(也可以規定返回0,只要讓以下代碼中maxsum初始化為0即可,此時我們要注意-1 0 0 0 -2這種情形,特別是如果要求輸出子數組的起始位置時,如果是面試就要和面試官問清楚)
以下代碼我們在PAT 1007. Maximum Subsequence Sum測試通過,測試main函數如下
int main()
{
int n;
scanf("%d", &n);
vector<int>vec(n);
for(int i = 0; i < n; i++)
scanf("%d", &vec[i]);
int left, right;
int maxsum = maxSum1(vec, left, right);//測試時替換函數名稱
if(maxsum >= 0)
printf("%d %d %d", maxsum, vec[left], vec[right]);
else printf("0 %d %d", vec[0], vec[n-1]);
}
參考:編程之美2.14 求數組的子數組之和的最大值
算法1:最簡單的就是窮舉所有的子數組,然后求和,復雜度是O(n^3)
int maxSum1(vector<int>&vec, int &left, int &right)
{
int maxsum = INT_MIN, sum = 0;
for(int i = 0; i < vec.size(); i++)
for(int k = i; k < vec.size(); k++)
{
sum = 0;
for(int j = i; j <= k; j++)
sum += vec[j];
if(sum > maxsum)
{
maxsum = sum;
left = i;
right = k;
}
}
return maxsum;
}
算法2: 上面代碼第三重循環做了很多的重復工作,稍稍改進如下,復雜度為O(n^2)
int maxSum2(vector<int>&vec, int &left, int &right)
{
int maxsum = INT_MIN, sum = 0;
for(int i = 0; i < vec.size(); i++)
{
sum = 0;
for(int k = i; k < vec.size(); k++)
{
sum += vec[k];
if(sum > maxsum)
{
maxsum = sum;
left = i;
right = k;
}
}
}
return maxsum;
}
算法3: 分治法, 下面貼上編程之美的解釋, 復雜度為O(nlogn)
//求數組vec【start,end】的最大子數組和,最大子數組邊界為[left,right]
int maxSum3(vector<int>&vec, const int start, const int end, int &left, int &right)
{
if(start == end)
{
left = start;
right = left;
return vec[start];
}
int middle = start + ((end - start)>>1);
int lleft, lright, rleft, rright;
int maxLeft = maxSum3(vec, start, middle, lleft, lright);//左半部分最大和
int maxRight = maxSum3(vec, middle+1, end, rleft, rright);//右半部分最大和
int maxLeftBoeder = vec[middle], maxRightBorder = vec[middle+1], mleft = middle, mright = middle+1;
int tmp = vec[middle];
for(int i = middle-1; i >= start; i--)
{
tmp += vec[i];
if(tmp > maxLeftBoeder)
{
maxLeftBoeder = tmp;
mleft = i;
}
}
tmp = vec[middle+1];
for(int i = middle+2; i <= end; i++)
{
tmp += vec[i];
if(tmp > maxRightBorder)
{
maxRightBorder = tmp;
mright = i;
}
}
int res = max(max(maxLeft, maxRight), maxLeftBoeder+maxRightBorder);
if(res == maxLeft)
{
left = lleft;
right = lright;
}
else if(res == maxLeftBoeder+maxRightBorder)
{
left = mleft;
right = mright;
}
else
{
left = rleft;
right = rright;
}
return res;
}
算法4: 動態規划, 數組為vec[],設dp[i] 是以vec[i]結尾的子數組的最大和,對於元素vec[i+1], 它有兩種選擇:a、vec[i+1]接着前面的子數組構成最大和,b、vec[i+1]自己單獨構成子數組。則dp[i+1] = max{dp[i]+vec[i+1], vec[i+1]}
如果不考慮記錄最大子數組的位置,於是有以下代碼: 本文地址
int maxSum_(vector<int>&vec)
{
int maxsum = INT_MIN, sum = 0;
for(int i = 0; i < vec.size(); i++)
{
sum = max(sum + vec[i], vec[i]);
maxsum = max(maxsum, sum);
}
return maxsum;
}
對以上代碼換個寫法,並記錄最大子數組的位置
int maxSum4(vector<int>&vec, int &left, int&right)
{
int maxsum = INT_MIN, sum = 0;
int begin = 0;
for(int i = 0; i < vec.size(); i++)
{
if(sum >= 0)
{
sum += vec[i];
}
else
{
sum = vec[i];
begin = i;
}
if(maxsum < sum)
{
maxsum = sum;
left = begin;
right = i;
}
}
return maxsum;
}
如果數組是循環的,該如何呢
這時分兩種情形(圖中紅色方框表示求得的最大子數組,left、right分別是子數組的開始和結尾):
(1)如下圖最大的子數組沒有跨過vec[n-1]到vec[0], 這就是每循環的情況
(2)如下圖,最大的子數組跨過vec[n-1]到vec[0]
對於第二種情形,相當於從原數組中挖掉了一塊(vec[right+1], …, vec[left-1]) ,那么我們只要使挖掉的和最小即可,求最小子數組和最大子數組類似,代碼如下,以下代碼在九度oj1572首尾相連數組的最大子數組和通過測試(測試需要,以下代碼當數組全是負數時,輸出0):
int maxSumCycle(vector<int>&vec, int &left, int&right)
{
int maxsum = INT_MIN, curMaxSum = 0;
int minsum = INT_MAX, curMinSum = 0;
int sum = 0;
int begin_max = 0, begin_min = 0;
int minLeft, minRight;
for(int i = 0; i < vec.size(); i++)
{
sum += vec[i];
if(curMaxSum >= 0)
{
curMaxSum += vec[i];
}
else
{
curMaxSum = vec[i];
begin_max = i;
}
if(maxsum < curMaxSum)
{
maxsum = curMaxSum;
left = begin_max;
right = i;
}
///////////////求和最小的子數組
if(curMinSum <= 0)
{
curMinSum += vec[i];
}
else
{
curMinSum = vec[i];
begin_min = i;
}
if(minsum > curMinSum)
{
minsum = curMinSum;
minLeft = begin_min;
minRight = i;
}
}
if(maxsum >= sum - minsum)
return maxsum;
else
{
left = minRight+1;
right = minLeft-1;
return sum - minsum;
}
}
【版權聲明】轉載請注明出處:http://www.cnblogs.com/TenosDoIt/p/3698246.html




