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

C++で式に演算子を追加する問題の解法(バックトラッキング)

問題概要

0から9までの数字のみで構成された文字列と、1つの目標値が与えられます。数字の間に二項演算子「+」「-」「*」を挿入し、その式の評価結果が目標値と一致するような、考えられるすべての組み合わせを求めて返すのがこの問題です。

例えば、入力が「232」で目標値が8の場合、答えは ["2*3+2", "2+3*2"] となります。

アルゴリズムの流れ

この問題はバックトラッキング(深さ優先探索)を使って解きます。以下の手順に従います。

  • solve() というメソッドを定義します。引数は idx(現在のインデックス)、s(元の数字列)、curr(現在の計算結果)、target(目標値)、temp(構築中の式文字列)、mult(直前に適用した乗算の値)です。
  • idx が s のサイズ以上になった場合:
    • curr が target と一致していれば、temp を結果リスト ret の末尾に追加します。
    • その後、処理を終了して戻ります。
  • aux を空文字列として初期化します。
  • i を idx から s のサイズ未満まで1ずつ増やしながら繰り返します:
    • aux に s[i] を連結します。
    • aux[0] が '0' かつ aux のサイズが1より大きい場合(先頭に余分な0が付く場合)は、以降の処理を無視して次の反復へスキップします。
    • idx が 0 の場合(最初の数値):solve(i + 1, s, aux を整数に変換した値, target, aux, aux を整数に変換した値) を呼び出します。
    • それ以外の場合:
      • 「+」の場合:solve(i + 1, s, curr + stol(aux), target, temp + "+" + aux, stol(aux)) を呼び出します。
      • 「-」の場合:solve(i + 1, s, curr - stol(aux), target, temp + "-" + aux, -stol(aux)) を呼び出します。
      • 「*」の場合:solve(i + 1, s, curr - mult + mult * stol(aux), target, temp + "*" + aux, mult * stol(aux)) を呼び出します。
  • メインメソッドから solve(0, num, 0, target, 空文字列, 0) を呼び出します。
  • 最後に ret を返します。

乗算処理のポイント

乗算は加減算より優先順位が高いため、単純に curr に値を掛けることはできません。例えば「2+3」まで計算した状態でさらに「*2」を付けたい場合、正しい結果は「2+3*2」=8です。そこで、直前に加算・減算した分(mult)を一度巻き戻し、「curr - mult + mult × 新しい値」として再計算することで、演算子の優先順位を正しく反映しています。

実装例

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

#include <bits/stdc++.h>
using namespace std;
void print_vector(vector<auto> v){
    cout << "[";
    for(int i = 0; i<v.size(); i++){
       cout << v[i] << ", ";
    }
    cout << "]"<<endl;
}
typedef long long int lli;
class Solution {
    public:
    vector <string> ret;
    void solve(int idx, string s, lli curr, lli target, string temp, lli mult){
       //cout << temp << " " << curr << endl;
       if(idx >= s.size()){
          if(target == curr){
             ret.push_back(temp);
          }
          return;
       }
       string aux = "";
       for(int i = idx; i < s.size(); i++){
          aux += s[i];
          if(aux[0] == '0' && aux.size() > 1) continue;
          if(idx == 0){
             solve(i + 1, s, stol(aux), target, aux, stol(aux));
          } else {
             solve(i + 1, s, curr + stol(aux), target, temp + "+" + aux, stol(aux));
             solve(i + 1, s, curr - stol(aux), target, temp + "-" + aux, -stol(aux));
             solve(i + 1, s, curr - mult + mult * stol(aux), target, temp + "*" + aux, mult * stol(aux));
          }
       }
    }
    vector<string> addOperators(string num, int target) {
       solve(0, num, 0, target, "", 0);
       return ret;
    }
};
main(){
    Solution ob;
    print_vector(ob.addOperators("232", 8));
}

入力

"232", 8

出力

[2+3*2, 2*3+2]
  1. C++で学ぶ式ツリー(Expression Tree)の基本と具体例

    式ツリーとは何か式ツリー(Expression Tree)とは、二分木の一種であり、木の各ノードが「演算子」または「オペランド(被演算子)」のいずれかで構成される特殊なデータ構造です。数式を木構造として表現することで、コンパイラや電卓アプリなどが数式を効率的に解析・評価できるようになります。ノードの役割式ツリーにおける各ノードは、次のように役割が分かれています。葉ノード(リーフノード):オペランド(数値や変数)を表します。非葉ノード(内部ノード):演算子(+、-、*、/ など)を表します。つまり、計算の対象となる値は必ず葉に配置され、それらをどのように処理するかを示す演算子が親ノードとして上に

  2. C++で式ツリー(Expression Tree)を評価する方法|再帰を使った実装例

    本記事では、+、-、*、/ といった二項演算子から構成される式ツリー(Expression Tree)を評価し、その計算結果を返す問題について解説します。 式ツリーとは 式ツリーは二分木の一種であり、各ノードには演算子またはオペランド(被演算数)が格納されます。ノードの役割は次のように分けられます。 葉ノード:演算の対象となる値(オペランド)を保持します。 非葉ノード(内部ノード):実行すべき演算を表す二項演算子を保持します。 式ツリーを中順走査(in-order traversal)すると、元の中置記法の数式が復元できるのが特徴です。 例題で理解しよう 入力:次のような式ツリーが与えられ