C++でフリップゲームIIを解く:メモ化再帰による先手の必勝判定
問題概要
フリップゲームは、2人のプレイヤーが対戦するゲームです。「+」と「-」の2種類の文字のみからなる文字列が与えられ、プレイヤー1とプレイヤー2が交互に、連続する2文字の「++」を「--」へと反転していきます。自分の番に打てる手がなくなったプレイヤーが負けとなり、相手が勝者となります。
この記事では、先手のプレイヤーが必ず勝利できるかどうかを判定する関数を定義します。
例えば、入力が s = "++++" の場合、出力は true(1)になります。先手のプレイヤーは中央の「++」を反転して「+--+」という局面を作れば、その後どちらに相手が打っても必ず勝てるからです。
解法アプローチ
この問題はメモ化再帰(Memoization)を使うことで効率的に解けます。基本的な考え方は次の通りです。
- 現在の局面から取り得るすべての手を試す
- ある手を打った後、相手が負ける局面になれば、その手は勝ちにつながる
- 一度計算した局面の結果はマップに保存し、再計算を避ける
アルゴリズムの手順
- メモ化用のマップ
memoを定義する - 関数
solve()を定義する(引数は文字列 s) - s が memo に登録済みなら、
memo[s]をそのまま返す possible := falseで初期化し、n を文字列の長さとする- i = 0 から n - 2 までループする:
- s[i] と s[i + 1] がどちらも '+' の場合:
- その2文字を '-' に変更して手を打つ
possible |= !solve(s)—— 相手側の結果が false なら自分の勝ち- 2文字を '+' に戻して局面を復元(バックトラック)
- possible が true になった時点で、
memo[s]に保存して即座に返す
- s[i] と s[i + 1] がどちらも '+' の場合:
- ループ終了後も勝ち筋がなければ、
memo[s] := possible(false)を返す - メイン処理からは
solve(s)の結果を返す
C++での実装例
以下のコードで実際の動作を確認できます。
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
unordered_map <string, bool> memo;
bool solve(string s){
if (memo.count(s))
return memo[s];
bool possible = false;
int n = s.size();
for (int i = 0; i < n - 1; i++) {
if (s[i] == '+' && s[i + 1] == '+') {
s[i] = '-';
s[i + 1] = '-';
possible |= !solve(s);
s[i] = '+';
s[i + 1] = '+';
if (possible)
return memo[s] = possible;
}
}
return memo[s] = possible;
}
bool canWin(string s) {
return solve(s);
}
};
main(){
Solution ob;
cout << (ob.canWin("++++"));
}入力
"++++"
出力
1
計算量について
メモ化により、同じ局面を複数回評価することが避けられるため、探索空間が大幅に削減されます。ただし、局面の総数は文字列長に対して指数的に増える可能性があるため、非常に長い入力には注意が必要です。実務的には、短〜中程度の長さの文字列に対して十分高速に動作します。
-
C++でJump Game IVを解く:BFSによる最小ジャンプ回数の求め方
問題の概要 整数型の配列 arr が与えられ、最初はインデックス 0 にいるものとします。1ステップごとに、次のいずれかの方法でジャンプが可能です。 インデックス i から i + x へ移動(条件:i + x < n) インデックス i から i - x へ移動(条件:i - x >= 0) arr[i] と arr[j] が同じ値で、i と j が異なる場合、i から j へ移動 ここで n は配列のサイズです。この問題の目的は、配列の最後のインデックスに到達するために必要な最小ジャンプ回数を求めることです。 入力例と出力 たとえば、入力が次のとおりだったとします。 {20
-
C++で解く「ジャンプゲームV」:メモ化再帰による最大訪問インデックス数の求め方
問題の概要整数型の配列 arr と整数 d が与えられます。1ステップごとに、インデックス i から次の場所へジャンプできます。右方向: i + x(ただし i + x < n、かつ x は 1 以上 d 以下)左方向: i - x(ただし i - x >= 0、かつ x は 1 以上 d 以下)ここで n は配列のサイズです。さらに重要な制約として、インデックス i から j へジャンプできるのは、arr[i] > arr[j] であり、かつ i と j の間にあるすべてのインデックス k に対して arr[i] > arr[k] を満たす場合のみです。つまり、より低