C++で循環配列の最大部分配列和を求めるアルゴリズム
問題概要
整数からなる循環配列 C(固定バッファ A で表現)が与えられます。このとき、C の空でない部分配列の合計として考えられる最大値を求めてください。ただし、部分配列に含められるのはバッファ A の各要素につき最大1回までです。例えば、配列が [1, -2, 3, -2] の場合、出力は 3 になります。これは部分配列 [3] の合計が 3 であり、これが最大であるためです。
解法のアプローチ
この問題を解くために、以下の手順に従います。
n := v のサイズとする
サイズ n の配列 leftSum、leftSumMax、rightSum、rightSumMax を作成する
leftSum[0] := v[0]、leftSumMax[0] := max(0, v[0]) とする
i を 1 から n − 1 まで繰り返す
leftSum[i] := leftSum[i − 1] + v[i]
leftSumMax[i] := max(leftSum[i], leftSumMax[i − 1])
rightSum[n − 1] := v[n − 1]、rightSumMax[n − 1] := max(0, v[n − 1]) とする
i を n − 2 から 0 まで降順で繰り返す
rightSum[i] := rightSum[i + 1] + v[i]
rightSumMax[i] := max(rightSumMax[i + 1], rightSum[i])
leftAns := leftSum[0] + rightSumMax[1]
i を 1 から n − 2 まで繰り返す
leftAns := max(leftAns, leftSum[i] + rightSumMax[i + 1])
rightAns := rightSum[n − 1] + leftSumMax[n − 2]
i を n − 2 から 1 まで降順で繰り返す
rightAns := max(rightAns, rightSum[i] + leftSumMax[i − 1])
curr := v[0]、kadane := v[0]
i を 1 から n − 1 まで繰り返す
curr := max(v[i], curr + v[i])
kadane := max(curr, kadane)
max(leftAns, rightAns, kadane) を返す
アルゴリズムのポイント
循環配列における最大部分配列和は、大きく分けて2つのケースを考える必要があります。1つ目は配列の範囲内で完結する通常の部分配列であり、これは Kadane(カダネ)のアルゴリズムで効率よく求められます。2つ目は配列の末尾と先頭をつなぐ「折り返し」を含む部分配列であり、こちらは左側からの累積和(leftSum)と右側からの累積和(rightSum)を組み合わせることで求められます。最終的な答えは、これら2つのケースの結果のうち大きい方となります。計算量は時間・空間ともに O(n) です。
それでは、以下の実装例を見て理解を深めましょう。
実装例
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
int maxSubarraySumCircular(vector<int>& v) {
int n = v.size();
vector <int> leftSum(n),leftSumMax(n),rightSum(n), rightSumMax(n);
leftSum[0] = v[0];
leftSumMax[0] = max((int)0,v[0]);
for(int i =1;i<n;i++){
leftSum[i] = leftSum[i-1] + v[i];
leftSumMax[i] = max(leftSum[i],leftSumMax[i-1]);
}
rightSum[n-1] = v[n-1];
rightSumMax[n-1] = max((int)0,v[n-1]);
for(int i =n-2;i>=0;i--){
rightSum[i] = rightSum[i+1]+v[i];
rightSumMax[i] = max(rightSumMax[i+1],rightSum[i]);
}
int leftAns=leftSum[0]+rightSumMax[1];
for(int i =1;i<n-1;i++){
leftAns = max(leftAns,leftSum[i]+rightSumMax[i+1]);
}
int rightAns = rightSum[n-1]+leftSumMax[n-2];
for(int i =n-2;i>=1;i--){
rightAns = max(rightAns,rightSum[i]+leftSumMax[i-1]);
}
int curr=v[0];
int kadane = v[0];
for(int i =1;i<n;i++){
curr = max(v[i],curr+v[i]);
kadane = max(curr,kadane);
}
return max(leftAns,max(rightAns,kadane));
}
};
main(){
vector<int> v = {1,-2,3,-2};
Solution ob;
cout << (ob.maxSubarraySumCircular(v));
}
入力
[1,-2,3,-2]
出力
3
-
C++で厳密に増加する部分配列の最大和を求めるアルゴリズム
問題の概要n 個の整数からなる配列が与えられたとき、その中に存在する「厳密に増加する(strictly increasing)部分配列」の中で、要素の合計が最大となるものを求めます。例として、次のような配列を考えてみましょう。[1, 2, 3, 2, 5, 1, 7]この配列には、厳密に増加している部分配列が3つ存在します。{1, 2, 3}{2, 5}{1, 7}それぞれの合計は 6、7、8 となり、この中で最大となるのは {1, 7} の合計 8 です。解き方の考え方この問題は、現在の部分配列の合計(current_sum)とこれまでの最大合計(max_sum)を追跡しながら配列を一度だけ
-
【C++】分割統治法で最大部分配列和を求める方法を解説
正の値と負の値が混在する数列が与えられたとき、その中から「要素が連続する部分配列(サブアレイ)」のうち合計が最大になるものを求める問題を考えます。例えば、数列 {-2, -5, 6, -2, -3, 1, 5, -6} の場合、最大部分配列和は 7 となり、これは {6, -2, -3, 1, 5} の合計に相当します。この問題は、分割統治法(Divide and Conquer)を用いることで効率的に解くことができます。アルゴリズムの手順配列を中央で2つに分割する以下の3つの値のうち最大のものを求める左側の部分配列における最大部分配列和右側の部分配列における最大部分配列和中央をまたいで(左右