Kadane's Algorithm -部分和の最大値を求める-
はじめに
本記事では配列の連続する部分列のうちその総和の最大値を求めるアルゴリズムであるKadane's Algorithmを解説します。使用する言語はC++です。
問題設定
以下のような問題を解決する.
長さ
の整数列 N が与えられる. A=\{A_0, A_1,...,A_{N-1}\}
この時連続する部分列でその総和が最大となる時その値を求めよ.
つまり
長さ
の整数列 N を満たす整数 A=\{A_0, A_1,...,A_{N-1}\}, 0≤l≤r<N について, l, r \sum_{i=l}^{r}A_i
の最大値を求めよ.
という問題である.
以下では部分列の総和を部分和という.
ここで
- 入力は全て整数
1\le N\le 3×10^5 \textcolor{red}{-10^6}\le A_i\le 10^6
を追加する.
イメージできない人のための具体例
の時,
を選択すると, その総和は
これを時間計算量
力ずく探索
一番力ずくな解法として全ての
力ずく探索の計算量解析
つまりこの計算量は
Kadane's Algorithm
そこでKadane's Algorithmの登場である.
このアルゴリズムのメインのロジックはDP(動的計画法)である.
一次元の大きさ
遷移を考えていく. 次の時点(
のどちらかである. これらのどちらが選択されるかというと,
-
\mathrm{DP[i]=DP[i-1]+A[i]}
現時点で最大値の候補としている部分列( まで続いているもの)の最後尾に\mathrm{A[i-1]} を付け足した部分列\mathrm{A[i]} -
\mathrm{DP[i]=A[i]}
ここまでの部分列の最大値が負の時に 単体を部分列と取る.\mathrm{A[i]}
ここから1. を伸ばしていくイメージ.

上の図において水色の続いてきている部分列(
それより緑単体の方が大きければ
あとは, DP[i]つまり
”
の中から最大値を探せば良い.
DPの初期化は問題に依って異なり, 求める部分列について,
- 長さ0(
)が許容されている\mathrm{l=r}
この時は長さ0の部分列も解の候補であるから,
\mathrm{DP[i]=0} - 長さ1以上(
)\mathrm{l<r}
この時は
\mathrm{DP[i]=A[0]}
で最大値を初期化する.
実装
int main(){
vector<int> A = {-3, -1, 3, 8, 0, 2, -6, 8, -7, -1};
int N = A.size();
vector<int> DP(N);
DP[0] = A[0];
for(int i = 1; i < N; i++){
DP[i] = max(DP[i-1] + A[i], A[i]);
}
int ans=0;
for(int i = 0; i < N; i++){
ans = max(ans, DP[i]);
}
cout << ans << endl;
}
というふうになる. 0項が許容のときのコードは書いてみてください。
ここで
int main(){
vector<int> A = {-3, -1, 3, 8, 0, 2, -6, 8, -7, -1};
int N = A.size();
int sum = 0, ans = 0;
for(int i = 0; i < N; i++){
// 項数0が許容されていないなら,
// int sum = A[0], ans = A[0];
// for(int i = 1; i < N; i++){
sum = max(sum + A[i], A[i]);
ans = max(ans, sum);
}
cout << ans << endl;
}
というふうになる. 実際に実行すると
g++ main.cpp
./a.out
15
という結果を得ることができる.
これを見ると配列
の A 個の要素に一度ずつアクセスしているので時間計算量は N となることが確認できる. O(N)
例題
最後に
初めてKadane's Algorithmを使う問題に出会った時、こういう実装をしたいということは思いついたのですがそれをうまく実装することができなかったのを覚えています。同じ悩みを持った人がこの記事を読んで救われたら幸いです。ここまで読んでくれてありがとうございました✨✨
わかりにくかったり、間違っている部分があったらぜひ教えてください!
そういえば、"カダネ"じゃなくて"カデイン"って読むとか読まないとか(真偽不明)
Discussion