題目:
★實驗任務:為了打破進了實驗室就嫁不出去的詛咒,六一兒童節這天集訓隊特地舉辦了一場相親大會,來自各個學院的n個姑娘在實驗室內站成一排。每個姑娘有自己的顏值ai。單身狗們決定邀請顏值之和最高的k個(k要大於0)位置相鄰的姑娘一起晚上的狼人殺。
★數據輸入:輸入第一行為一個數n(1<=n<=100000)表示姑娘個數。接下來一行有n個整數ai(-1000<=ai<=1000)表示第i個姑娘的顏值。
★數據輸出:輸出一行為最大連續子串和。
輸入示例:5 6 -1 5 4 -7
輸出示例:14
輸入示例:7 0 6 -1 1 -6 7 -5
輸出示例:7
三種做法:
1.O(n^2)
//
// main.cpp
// MaxSubSequence
//
// Created by wasdns on 16/8/31.
// Copyright © 2016年 wasdns. All rights reserved.
//
#include <iostream>
#include <cstdio>
#include <string.h>
#include <string>
#include <algorithm>
using namespace std;
int number[1000005];
int MaxSubSequence(int n) //復雜度為O(n^2)
{
int MaxSum = 0;
for(int i = 0; i < n; i++)
{
int Cal = 0;
for(int j = i; j < n; j++)
{
Cal += number[j];
if(Cal > MaxSum) //利用先前計算的結果進行比較
{
MaxSum = Cal;
}
}
}
return MaxSum;
};
int main()
{
int n, i;
cin >> n;
for(i = 0; i < n; i++)
{
cin >> number[i];
}
int MaxSum = MaxSubSequence(n);
cout << MaxSum << endl;
return 0;
}
2.O(nlogn)
//
// main.cpp
// MaxSubSequence_2
//
// Created by wasdns on 16/8/31.
// Copyright © 2016年 wasdns. All rights reserved.
//
#include <iostream>
#include <cstdio>
#include <string>
#include <string.h>
#include <algorithm>
using namespace std;
int number[1000005];
int MaxCalculator(int left, int right);
int MaxSubSequence(int left, int right)
{
return MaxCalculator(left, right);
}
int CalMax(int a, int b, int c)
{
if(a > b)
{
if(a > c) return a;
else return c;
}
else
{
if(b > c)return b;
else return c;
}
}
int MaxCalculator(int left, int right)
{
if(left == right)
{
if(number[left] > 0) return number[left];
else return 0;
}
int MaxLeftSum = 0;
int MaxRightSum = 0;
int middle;
//cout << left << " " << right << endl;
middle = (left + right)/2;
//cout << middle << endl;
MaxLeftSum = MaxCalculator(left, middle);
MaxRightSum = MaxCalculator(middle + 1, right); //沒有+1:導致 0 1 循環
int MLASum = 0; //MaxLeftAreaSum
int MRASum = 0; //MaxRightAreaSum
int MSum = 0;
for(int i = middle; i >= left; i--)
{
MSum += number[i];
if(MSum > MLASum) MLASum = MSum;
}
MSum = 0;
for(int i = middle + 1; i <= right; i++)
{
MSum += number[i];
if(MSum > MRASum) MRASum = MSum;
}
return CalMax(MaxLeftSum, MaxRightSum, MLASum + MRASum);
}
int main()
{
int n, i;
cin >> n;
for(i = 0; i < n; i++)
{
cin >> number[i];
}
cout << MaxSubSequence(0, n-1) << endl;
return 0;
}
3.O(nlogn):常用的動態規划
//
// main.cpp
// MaxSubSequence_3
//
// Created by wasdns on 16/8/31.
// Copyright © 2016年 wasdns. All rights reserved.
//
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
#include <string.h>
using namespace std;
int Number[100005];
int b[100005]; //含當前位置元素的最大子序列和 不斷的更新
int MSS(int n) //狀態轉移方程:b[i] = MAX{b[i-1] + a[i], a[i]};
{
memset(b, 0, sizeof(b));
int i;
int sum = Number[0]; //sum 初始化為 Number[0]
b[0] = Number[0];
for(i = 1; i < n; i++)
{
if(b[i-1] + Number[i] > Number[i])
{
b[i] = b[i-1] + Number[i];
}
else b[i] = Number[i];
//cout << "b[i] = " << b[i] << endl;
if(sum < b[i]) sum = b[i];
//cout << "sum = " << sum << endl;
}
return sum;
}
int main()
{
int n;
cin >> n;
for(int i = 0; i < n; i++)
{
cin >> Number[i];
}
int MaxSequenceSum = MSS(n);
cout << MaxSequenceSum << endl;
return 0;
}
小結
給出的第一種解決代碼,首先復雜度相比使用三個for循環的O(N^3)下降了很多,但是仍然達不到要求。利用的是 之前計算的結果,從當前位置一個一個加過去。
實現算法:
int MaxSubSequence(int n) //復雜度為O(n^2)
{
int MaxSum = 0;
for(int i = 0; i < n; i++)
{
int Cal = 0;
for(int j = i; j < n; j++)
{
Cal += number[j];
if(Cal > MaxSum) //利用先前計算的結果進行比較
{
MaxSum = Cal;
}
}
}
return MaxSum;
};
第一種易於理解,但是算法復雜度過高。因此為了適應題目的要求,提到了第二種的解決方法。
算法實現:最大子序列和,要么出現在 1)left 和 middle 之間,要么出現2)在 middle 和 right 之間,還有 3)middle在這個子序列中。一共三種情況,分別計算出三種情況的最大子序列的大小,取最大值。
前面兩種方法,可以使用 遞歸分治 的思想:更新middle -> 計算上面三種情況的子序列大小 -> 利用遞歸 -> 更新middle ···
當最后 left 和 middle 重合的時候(或者 middle 和 right 重合的時候),判斷 number[left] 是否大於0,大於0返回number[left],小於0返回0。
當利用遞歸 計算完成1)和2)的值之后,轉而計算3)的值:左邊從middle開始遍歷,找到left;右邊從middle+1開始遍歷,找到right;兩邊的值相加即可求得3)。
實現代碼:
int CalMax(int a, int b, int c)
{
if(a > b)
{
if(a > c) return a;
else return c;
}
else
{
if(b > c)return b;
else return c;
}
}
int MaxCalculator(int left, int right)
{
if(left == right) //遞歸終止的條件
{
if(number[left] > 0) return number[left];
else return 0;
}
int MaxLeftSum = 0;
int MaxRightSum = 0;
int middle;
//cout << left << " " << right << endl;
middle = (left + right)/2;
//cout << middle << endl;
MaxLeftSum = MaxCalculator(left, middle);
MaxRightSum = MaxCalculator(middle + 1, right); //注意!沒有+1:導致 0 1 循環
int MLASum = 0; //MaxLeftAreaSum
int MRASum = 0; //MaxRightAreaSum
int MSum = 0;
for(int i = middle; i >= left; i--) //從middle左邊開始遍歷
{
MSum += number[i];
if(MSum > MLASum) MLASum = MSum;
}
MSum = 0;
for(int i = middle + 1; i <= right; i++) //從middle+1右邊開始遍歷
{
MSum += number[i];
if(MSum > MRASum) MRASum = MSum;
}
return CalMax(MaxLeftSum, MaxRightSum, MLASum + MRASum); //取三種情況的最大值
}
第三種,即最常見的動態規划問題了。動態規划是一種利用之前計算結果的算法,我們這里使用了b[i]數組來存儲:b[i]代表的意思是,經過number[i]的最大子序列。
如果b[i-1]+number[i]大於number[i],那么加到此處的最大子序列b[i]更新為b[i-1]+number[i];否則,將b[i]更新為number[i]重新開始。記錄整個過程中的最大子序列和sum。狀態轉移方程:b[i] = MAX{b[i-1]+number[i], number[i]}
注意:sum需要初始化為Number[0]。
實現代碼:
int MSS(int n) //狀態轉移方程:b[i] = MAX{b[i-1] + a[i], a[i]};
{
memset(b, 0, sizeof(b));
int i;
int sum = Number[0]; //sum 初始化為 Number[0]
b[0] = Number[0];
for(i = 1; i < n; i++)
{
if(b[i-1] + Number[i] > Number[i])
{
b[i] = b[i-1] + Number[i];
}
else b[i] = Number[i];
//cout << "b[i] = " << b[i] << endl;
if(sum < b[i]) sum = b[i];
//cout << "sum = " << sum << endl;
}
return sum;
}
2016/8/31