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

C++で組み合わせをすべて生成する方法【バックトラッキング解説】

問題概要

2つの整数 nk が与えられたとき、1 から n までの数字の中から k 個を選んで作れるすべての組み合わせを求めます。
例えば、n = 4、k = 2 の場合、答えは [[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]] となります。

解法の考え方:バックトラッキング

この種の問題は、バックトラッキング(探索の巻き戻し)と呼ばれる手法で効率的に解くことができます。再帰関数を使って候補の数字を一つずつ選びながら組み合わせを構築し、条件を満たした時点で結果を保存していきます。

アルゴリズムの手順

  • 再帰関数 solve() を用意します。引数は n、k、現在の組み合わせを保持する一時配列 temp、開始位置 start(start の初期値は 1)です。
  • temp のサイズが k に達したら、temp を結果格納用の res 配列に追加して再帰を抜けます。
  • i を start から n まで順に処理します。
    • temp に i を追加する
    • solve(n, k, temp, i + 1) を再帰的に呼び出す
    • temp の末尾から要素を削除する(バックトラック)
  • solve(n, k, [](空の配列)) の形で最初の呼び出しを行います。
  • 最後に res を返します。

再帰呼び出し時に start を「i + 1」に設定するのがポイントです。これにより、同じ数字の重複や [2,1] のような順序違いの重複を自然に防ぐことができます。

C++による実装例

以下のコードで実際の動作を確認してみましょう。

#include <bits/stdc++.h>
using namespace std;
void print_vector(vector<vector<int> > v){
    cout << "[";
    for(int i = 0; i<v.size(); i++){
       cout << "[";
       for(int j = 0; j <v[i].size(); j++){
          cout << v[i][j] << ", ";
       }
       cout << "],";
    }
    cout << "]"<<endl;
}
class Solution {
public:
    vector < vector <int> > res;
    void solve(int n, int k, vector <int> temp, int start = 1){
       if(temp.size() == k){
          res.push_back(temp);
          return;
       }
       for(int i = start; i <= n; i++){
          temp.push_back(i);
          solve(n, k, temp, i + 1);
          temp.pop_back();
       }
    }
    vector<vector<int> > combine(int n, int k) {
       res.clear();
       vector <int> temp;
       solve(n ,k, temp);
       return res;
    }
};
main(){
    Solution ob;
    print_vector(ob.combine(5,3));
}

入力

5
3

出力

[[1,2,3],[1,2,4],[1,2,5],[1,3,4],[1,3,5],[1,4,5],[2,3,4],[2,3,5],[2,4,5],[3,4,5]]

計算量について

n 個から k 個を選ぶ組み合わせの総数は、二項係数 C(n, k) = n! / (k! × (n−k)!) で表されます。そのため、時間計算量は O(C(n, k) × k)、空間計算量も同様に O(C(n, k) × k) となります。n や k が大きくなると組み合わせの総数は急激に増加するため、実用上は n が 20 程度までの入力を目安にするとよいでしょう。

  1. C++で点集合の線対称(ラインリフレクション)を判定するアルゴリズム

    問題概要2次元平面上にn個の点が与えられます。このとき、y軸に平行な直線で全ての点を鏡映(反射)した結果が、元の点集合と完全に一致するような直線が存在するかどうかを判定します。言い換えれば、ある直線を対称軸として全ての点を反転させたとき、反転後の点の集合が元の集合と同一になるかを確認する問題です。例えば、入力が points = [[1,1],[-1,1]] の場合を考えてみましょう。この場合、x = 0 の直線(y軸)を対称軸とすると、点 (1,1) は (-1,1) へ、(-1,1) は (1,1) へと移ります。点集合全体としては変化がないため、出力は true となります。解法のポイン

  2. C++で解く対角トラバースII:リストのリストを対角順に出力する方法

    問題の概要 「リストのリスト」である nums が与えられたとき、そのすべての要素を対角順(ダイアゴナルオーダー)に並べて出力するのがこの問題の目的です。 たとえば、次のような行ごとに長さの異なる配列(ジャグ配列)が入力として与えられた場合を考えてみましょう。 このとき、期待される出力は次のとおりです。 [1, 6, 2, 8, 7, 3, 9, 4, 12, 10, 5, 13, 11, 14, 15, 16] 解法のアプローチ この問題は、各要素を「値と座標のセット」として一旦記録し、対角線ごとの順序になるようにソートし直すことで解けます。具体的な手順は以下の通りです。 結果を格納す