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]
-
C++で学ぶ式ツリー(Expression Tree)の基本と具体例
式ツリーとは何か式ツリー(Expression Tree)とは、二分木の一種であり、木の各ノードが「演算子」または「オペランド(被演算子)」のいずれかで構成される特殊なデータ構造です。数式を木構造として表現することで、コンパイラや電卓アプリなどが数式を効率的に解析・評価できるようになります。ノードの役割式ツリーにおける各ノードは、次のように役割が分かれています。葉ノード(リーフノード):オペランド(数値や変数)を表します。非葉ノード(内部ノード):演算子(+、-、*、/ など)を表します。つまり、計算の対象となる値は必ず葉に配置され、それらをどのように処理するかを示す演算子が親ノードとして上に
-
C++で式ツリー(Expression Tree)を評価する方法|再帰を使った実装例
本記事では、+、-、*、/ といった二項演算子から構成される式ツリー(Expression Tree)を評価し、その計算結果を返す問題について解説します。 式ツリーとは 式ツリーは二分木の一種であり、各ノードには演算子またはオペランド(被演算数)が格納されます。ノードの役割は次のように分けられます。 葉ノード:演算の対象となる値(オペランド)を保持します。 非葉ノード(内部ノード):実行すべき演算を表す二項演算子を保持します。 式ツリーを中順走査(in-order traversal)すると、元の中置記法の数式が復元できるのが特徴です。 例題で理解しよう 入力:次のような式ツリーが与えられ