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

C++で解く:カードから取得できる最大ポイントの求め方

複数のカードが一列に並んでおり、各カードにはポイントが割り当てられています。これらのポイントは整数配列 cardPoints として与えられます。各ステップで、列の先頭または末尾からカードを1枚取得でき、ちょうど k 枚のカードを取得しなければなりません。最終的なスコアは、取得したカードのポイントの合計になります。ここでの課題は、整数配列 cardPoints と整数 k が与えられたときに、獲得できる最大スコアを求めることです。

例で理解する

たとえば、入力が cardPoints = [1,2,3,4,5,6,1]、k = 3 の場合、出力は 12 になります。最初の1枚は、左端・右端どちらを取ってもスコアは1です。そこで、先に右端のカードを選ぶことで合計スコアが最大化されます。最適な戦略は右側の3枚を取ることで、最終スコアは 1 + 6 + 5 = 12 となります。

解法のアプローチ

この問題は累積和(プレフィックスサム)を用いることで効率的に解けます。基本的な考え方は、「左端から取る枚数」を0〜k枚の範囲で変化させながら、残りの枚数を右端から取るケースをすべて評価することです。以下の手順に従います。

  • 2つの配列 pre1 と pre2 を定義し、元の配列 v で初期化します。
  • ret := 0 とします。
  • n を配列 v のサイズとします。
  • i := 1 から始めて i < n の間、i を1ずつ増やしながら pre1[i] := pre1[i] + pre1[i - 1] を実行し、左からの累積和を作ります。
  • i := n - 2 から始めて i >= 0 の間、i を1ずつ減らしながら pre2[i] := pre2[i] + pre2[i + 1] を実行し、右からの累積和を作ります。
  • k >= n の場合は、すべてのカードを取得できるため pre1[n - 1] を返します。
  • i := k - 1 とし、ret := pre1[i] で初期化します(左端のみからk枚取る場合)。
  • i を1減らし、j := n - 1 とします。
  • i >= 0 の間、ret := max(ret, pre1[i] + pre2[j]) を計算し、i と j を1ずつ減らします。これは「左から i+1 枚、右から残りを取る」分割を順に試す操作に相当します。
  • 最後に ret := max(ret, pre2[n - k]) とします(右端のみからk枚取る場合)。
  • ret を返します。

C++による実装例

以下の実装を見ると、より理解が深まるでしょう。

#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
    int maxScore(vector<int>& v, int k) {
        vector<int> pre1(v.begin(), v.end());
        vector<int> pre2(v.begin(), v.end());
        int ret = 0;
        int n = v.size();
        for (int i = 1; i < n; i++) {
            pre1[i] += pre1[i - 1];
        }
        for (int i = n - 2; i >= 0; i--) {
            pre2[i] += pre2[i + 1];
        }
        if (k >= n) {
            return pre1[n - 1];
        }
        int i = k - 1;
        ret = pre1[i];
        i--;
        int j = n - 1;
        while (i >= 0) {
            ret = max(ret, pre1[i] + pre2[j]);
            i--;
            j--;
        }
        ret = max(ret, pre2[n - k]);
        return ret;
    }
};
main(){
    Solution ob;
    vector<int> v = {1,2,3,4,5,6,1};
    cout << (ob.maxScore(v, 3));
}

入力

{1,2,3,4,5,6,1}

出力

12

計算量の目安

このアルゴリズムは配列を数回走査するだけで済むため、時間計算量は O(n) です。また、累積和のために2つの補助配列を使用するため、空間計算量も O(n) となります。取得の組み合わせごとに合計を都度計算する非効率な方法と比べ、前計算によって高速化できるのがこの手法の大きなメリットです。

  1. C++でN×Nチェス盤に配置できるビショップの最大数を求める方法

    問題概要チェス盤のサイズを表す整数 N が入力として与えられます。この問題では、任意の N に対して、N×N のチェス盤上に互いに攻撃し合わないようにビショップ(bishop)を最大何個配置できるかを求めます。まず、具体例を使って理解していきましょう。例1入力: N = 2出力: N×N チェス盤に配置できるビショップの最大数 ― 2説明: 2×2 のチェス盤の場合、互いに干渉しない位置は図示された場所のみです。つまり、2×2 の盤面に配置できるビショップは最大 2 個となります。例2入力: N = 5出力: N×N チェス盤に配置できるビショップの最大数 ― 8プログラムで使用するアプローチ

  2. C++で下から右方向へ光を伝送できる鏡の最大数を求める

    はじめに 本記事では、0と1だけで構成された正方行列が与えられたとき、「下から右方向へ光を伝送できる鏡」の最大数を求めるアルゴリズムをC++で解説します。 問題の定義 行列の各要素は次の意味を持ちます。 0 … 空きセル(何もない場所) 1 … 障害物 空きセルの中から鏡を設置できる場所を見つけ、それらの鏡が下から右へ光を伝送できるようにすることを目標とします。 具体的には、鏡がセル [i, j] に配置できるのは、同じ行 i の右側にあるすべてのセルと、同じ列 j の下側にあるすべてのセルに障害物が存在しない場合です。 言い換えると、A[i][j] に鏡を置くためには、A[i+1〜n