分割して征服する:最大のサブアレイの問題
質問:
配列が与えられたら、サブ配列の要素の合計が最大になるようにサブ配列(連続)を見つけます。
入力:
配列X [1 ... n]が与えられた場合、配列添え字l、r(l≤r)を持つ空でないサブ配列の任意のペアについて、合計はS(l、s)として記録されます。
出力:
Sを検索(l、s)の最大値はSmaxとして記録されます。
問題分析
- 配列X [1…n]をX [1…n / 2]とX [n / 2 + 1…n]に分割します
- サブ問題を再帰的に解決します
S1:配列X [1…n / 2]の最大のサブ配列
S2:配列X [n / 2 + 1…n]の最大のサブ配列 - サブ問題を組み合わせて、S max
S3:中間点全体で最大のサブアレイを取得します。アレイ
の最も王子様のサブアレイの合計X S max = max {S1、sz、s3}
実行効率のボトルネック値は、S3を解決する際の問題であるマージに関するものです。
S3ソリューション:
-
记mid = n / 2
-
S3は2つの部分に分けることができます:
左:X [mid]で終わる最大のサブアレイの合計
右:X [mid +1]で始まる最大のサブアレイの合計
S3 =左+右左の解決策:
X [mid]から前方に合計をトラバースし、最大値を記録します
。右の解決策:
X [mid +1]から後方に合計をトラバースし、最大値を記録します。
S3時間の複雑さ:
- 残り時間の複雑さ:O(mid)
- 適切な時間の複雑さ:O(n-mid)
- S3時間の複雑さ:O(n)
アルゴリズム分析図
コード
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 10000;
int q[N]; // 序列
// 求跨越中间的最大子序列
int findMidSum(int l, int mid, int r) {
int leftSum = -100000, rightSum = -100000; // 初始化为 -100000 是为了防止出现负数和的情况
int sum = 0; // sum 即向左和向右最大的子序列的和
for (int i=mid; i>=l; i--) {
sum += q[i];
// 从mid开始向左加不断更新最大值,最后计算出来的即mid左边最大的子序列的值
leftSum = max(leftSum, sum);
// max()函数为 #include<algorithm>中的方法,可以比较两个数的大小,返回较大的数
}
sum = 0;
for (int i=mid+1; i<=r; i++) {
sum += q[i];
// 从mid开始向右加不断更新最大值,最后计算出来的即mid右边最大的子序列的值
rightSum = max(rightSum, sum);
}
return leftSum + rightSum;
}
// 分治求左右两端最大的子序列和并将其与跨越中间的子序列的和比较大小,返回最大连续子序列的和
int maxSubArr(int l, int r) {
if (l == r) return q[l]; // 递归出口
int mid = l + r >> 1; //右移一位,相当于除2;也可以写成 mid = l+(r-l)/2;
int leftSum = maxSubArr(l, mid);
int rightSum = maxSubArr(mid+1, r);
int midMaxSum = findMidSum(l, mid, r);
int res = max(leftSum, rightSum);
res = max(res, midMaxSum);
// 返回最大的子序列和
return res;
}
int main() {
int n;
cin >> n;
for (int i=0; i<n; i++) scanf("%d", &q[i]);
int res = -100000;
res = maxSubArr(0, n-1);
cout << res;
return 0;
}