C++
 Computer >> コンピューター >  >> プログラミング >> C++

C++で解くK回連結配列の最大部分配列和

問題概要

整数型の配列 arr と整数 k が与えられます。まず、元の配列を k 回繰り返してつなげた新しい配列を作成します。たとえば、arr = [1, 2]k = 3 の場合、生成される配列は [1, 2, 1, 2, 1, 2] となります。

そのうえで、この配列における最大部分配列の合計を求めます。なお、部分配列の長さは 0 でもよく、その場合は合計を 0 とみなします。答えは非常に大きな値になる可能性があるため、10^9 + 7 で割った余りを返してください。

たとえば、入力が [1, -2, 1]k = 5 のとき、答えは 2 になります。

解き方の考え方

この問題は、連結後の巨大な配列を実際に構築しなくても、元の配列から次の 4 つの値を計算するだけで解けます。

  • カダネのアルゴリズムによる最大部分配列和(getKadane)

  • 配列全体の合計(getSum)

  • 先頭から始まる最大の累積和(接頭辞の最大値)(getPrefix)

  • 末尾で終わる最大の累積和(接尾辞の最大値)(getSuffix)

それぞれのメソッドは次のように動作します。

  • getKadane():ret := -inf、sum := 0 で初期化し、i を 0 から配列サイズ - 1 まで順に処理します(各値は mod 10^9 + 7 で管理)。

    • sum := max(arr[i], arr[i] + sum)

    • ret := max(ret, sum)

    最後に、ret が負であれば 0 を、そうでなければ ret を返します。

  • getSum():ret := 0 とし、配列の全要素を順に加算して返します。

  • getPrefix():先頭から累積和を取りながら、その最大値を記録します。結果が負なら 0 を返します。

  • getSuffix():末尾から累積和を取りながら、その最大値を記録します。結果が負なら 0 を返します。

メインの処理では、これらの値を組み合わせて次のように答えを決定します。

  • kadane := getKadane(arr)、sum := getSum(arr)、prefix := getPrefix(arr)、suffix := getSuffix(arr) を計算する

  • k == 1 の場合は、kadane をそのまま返す

  • sum > 0 の場合は、max( sum × (k − 2) + prefix + suffix, kadane ) を返す

  • それ以外の場合は、max( prefix + suffix, kadane ) を返す

sum が正のときは、中央に挟まれる (k − 2) 個分のコピーを丸ごと含めるのが得であり、その両端に prefix と suffix を付け加えることで最大値が得られます。sum が正でない場合は、最初と最後のコピーだけをつなげた範囲(prefix + suffix)と、単一コピー内の kadane を比較すれば十分です。

C++での実装例

理解を深めるために、以下の実装を見てみましょう。

#include <bits/stdc++.h>
using namespace std;
typedef long long int lli;
const int MOD = 1e9 + 7;
int add(lli a, lli b){
   return ((a % MOD) + (b % MOD)) % MOD;
}
int mul(lli a, lli b){
   return ((a % MOD) * (b % MOD)) % MOD;
}
class Solution {
   public:
   int getKadane(vector <int>& arr){
      int ret = INT_MIN;
      int sum = 0;
      for(int i = 0; i < arr.size(); i++){
         sum = max(arr[i], arr[i] + sum);
         ret = max(ret, sum);
         sum %= MOD;
         ret %= MOD;
      }
      return ret < 0? 0 : ret;
   }
   int getSum(vector <int>& arr){
      int ret = 0;
      for(int i = 0; i < arr.size(); i++){
         ret += arr[i];
         ret %= MOD;
      }
      return ret;
   }
   int getPrefix(vector <int>& arr){
      int ret = INT_MIN;
      int sum = 0;
      for(int i = 0; i <arr.size(); i++){
         sum += arr[i];
         sum %= MOD;
         ret = max(ret, sum);
         ret %= MOD;
      }
      return ret < 0 ? 0 : ret;
   }
   int getSuffix(vector <int>& arr){
      int sum = 0;
      int ret = INT_MIN;
      for(int i = arr.size() - 1; i >= 0 ; i--){
         sum += arr[i];
         ret = max(ret, sum);
         sum %= MOD;
         ret %= MOD;
      }
      return ret < 0 ? 0 : ret;
   }
   int kConcatenationMaxSum(vector<int>& arr, int k) {
      int kadane = getKadane(arr);
      int sum = getSum(arr);
      int prefix = getPrefix(arr);
      int suffix = getSuffix(arr);
      if(k == 1) return kadane;
      if(sum > 0){
         return max((int)mul((k-2) , sum) + prefix % MOD + suffix % MOD, kadane);
      } else {
         return max(add(prefix , suffix), kadane);
      }
   }
};
main(){
   vector<int> v1 = {1,-2,1};
   Solution ob;
   cout << (ob.kConcatenationMaxSum(v1, 5));
}

入力

[1,-2,1]
5

出力

2
  1. C++で二分木の最大スパイラル和を求める方法

    この記事では、二分木が与えられたときに、その最大スパイラル和(Maximum Spiral Sum)を求めるプログラムをC++で作成します。 スパイラル和とは? スパイラル和とは、二分木をスパイラル(ジグザグ)順に走査したときに通るノードの値の合計のことです。 スパイラル走査では、ノードを根(ルート)から葉に向かって辿ります。第1レベルは左から右へ、次のレベルは右から左へ、さらにその次はまた左から右へと、レベルごとに走査方向を交互に切り替えながら進むのが特徴です。 問題の例 例として、次のような二分木を考えてみましょう。 1 / \

  2. C++で配列を最大K個に分割して平均の合計を最大化する方法

    問題概要 数値の配列 A が与えられます。この配列を最大 K 個の隣接する(空でない)グループに分割し、スコアを「各グループの平均値の合計」と定義します。このとき、達成できる最大スコアを求めるのが本問題です。 入力例 入力配列が {9, 2, 5, 3, 10} の場合、たとえば次のように分割できます。 {9} {2, 5, 3} {10} このときの平均の合計は次のとおりです。 9 + (2 + 5 + 3) / 3 + 10 = 22.33 アルゴリズム(メモ化再帰) この問題は、メモ化(記憶化)再帰を使うことで効率よく解くことができます。 memo[i][k]:A[i]〜A[n-1]