連続する部分列の総和を最大にする方法を考えます。

間違ってはいないが時間がかかる

まずは何の工夫もない方法から。二重ループですべての部分列の総和を計算してそのなかから最大値を求めようとしています。与えられた配列が短ければこれでも問題ないのですが、配列の長さが100,000を超えるようなものを渡されるといくら時間があっても終わりません。

累積和を利用する

配列Aが与えられたとき、配列Bを作り、B[0] = 0を代入します。それ以降はB[n] = A[0]~A[n – 1]までの総和を代入します。もし int[] A = { 1, 2, 3, 2, 1 }なら int[] B = {0, 1, 3, 6, 8, 9 }です。この段階で配列が1つ大きくなっています。

もし配列Aのa番目からb番目(先頭は0番目)を計算する必要があるときは B[b] – B[a – 1] を計算します。これで同じ区間を何度も足し算する必要はなくなりました。

累積和から部分列の総和の最大値を求める

つぎに連続する部分列の総和を最大にする方法を考えます。このときに部分列の開始と終了を二重ループで求めていては時間が足りません。ここはこのように考えます。

変数 M と max を0で初期化します。B[i] が M よりも小さいときはその値で M を更新します。そして B[i] – M の値が max よりも大きいときは max を更新します。最終的な max の値が「連続する部分列の総和の最大値」となります。

要は累積和 B を使うと[a, b]の総和は B[b+1] – B[a](ただし b >= a)となるので、B[a]が小さいときが候補になります。そこで B[a] の暫定的最小値 M を求め、B[i] – Mが最大になるときが求めるべき総和の最大値となるわけです。

また最大値だけでなくその区間がどこからどこまでなのかも知りたいときはMを更新するときにtempLeftに値を保存しておき、max を更新するときに tempLeft に 1 足した値と i で left と right を更新します。

サンプルコードではデバッグ情報としていろいろ表示させましたが、実際には必要ないので以下のコードのほうがシンプルです。

連続する部分列の総和の最小値

連続する部分列の総和の最小値もこれで計算できます。