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

C++で増加部分列をすべて求めるアルゴリズム

問題の概要

整数型の配列が与えられたとき、その配列から作られるすべての異なる増加部分列を求めることを考えます。ただし、増加部分列の長さは2以上でなければなりません。例えば、配列が [4, 6, 7, 7] の場合、出力は次のようになります。

[[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7, 7], [4, 7, 7]]

解法のアプローチ

この問題は、バックトラッキング(深さ優先探索)を用いて効率的に解くことができます。手順は以下の通りです。

  • すべての結果を格納するための配列 res を定義します。
  • solve というメソッドを作成します。このメソッドは、nums 配列、開始位置 start、一時的な配列 temp を引数として受け取ります。
  • temp のサイズが 1 より大きい場合、tempres に追加します。
  • 同一階層での重複を防ぐための集合 visited(set)を作成します。
  • i を start から nums のサイズまでループさせます。
    • x := nums[i] とします。
    • x がすでに visited に含まれている場合は、以降の処理をスキップします。
    • xvisited に追加します。
    • temp が空であるか、temp の末尾の要素が x 以下である場合は、以下を実行します。
      • xtemp に追加します。
      • solve(nums, i + 1, temp) を再帰的に呼び出します。
      • temp の末尾から 1 要素を削除します(バックトラック)。
  • メイン関数から solve(nums, 0, temp) を呼び出します。
  • res を返します。

重複回避のポイント

各再帰呼び出し(同じ階層)内で、一度選択した値と同じ値を選ばないようにすることで、重複する部分列の生成を防いでいます。この仕組みにより、入力配列に重複した値が含まれていても、結果として得られる部分列が重複することはありません。

実装例(C++)

#include <bits/stdc++.h>
using namespace std;
void print_vector(vector<vector<auto> > 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( vector <int>& nums, int start, vector <int> temp){
       if(temp.size() > 1){
          res.push_back(temp);
       }
       set <int> visited;
       for(int i = start; i < nums.size(); i++){
          int x = nums[i];
          if(visited.count(x))continue;
          visited.insert(x);
          if(temp.empty() || temp[temp.size() - 1] <= x){
             temp.push_back(x);
             solve(nums, i + 1, temp);
             temp.pop_back();
          }
       }
    }
    vector<vector<int>> findSubsequences(vector<int>& nums) {
       res.clear();
       vector <int> temp;
       solve(nums, 0, temp);
       return res;
    }
};
main(){
    vector<int> v = {5,6,7,8};
    Solution ob;
    print_vector(ob.findSubsequences(v));
}

入力

[5, 6, 7, 8]

出力

[[5, 6],[5, 6, 7],[5, 6, 7, 8],[5, 6, 8],[5, 7],[5, 7, 8],[5, 8],[6, 7],[6, 7, 8],[6, 8],[7, 8]]

まとめ

このように、バックトラッキングと set による重複チェックを組み合わせることで、与えられた配列から長さ 2 以上の増加部分列をすべて漏れなく、かつ重複なく列挙することができます。計算量は部分列の候補数に依存するため、入力サイズが大きくなると指数的に増加しますが、この手法はこの種の列挙問題に対する標準的なアプローチです。

  1. 【C++】ターゲットに最も近い3つの数の合計を求めるアルゴリズム

    問題の概要n個の整数を含む配列 nums と、1つのターゲット値 target が与えられます。この中から3つの整数を選び、その合計がターゲットに最も近くなるような組み合わせを見つけ、その合計値を返すことが目的です。なお、各入力には必ず解が1つだけ存在すると仮定してよいものとします。例えば、配列が [-1, 2, 1, -4]、ターゲットが 1 の場合、最適な組み合わせは [-1, 2, 1] で、その合計は 2 となります。これがターゲットに最も近い合計値です。解法のアプローチこの問題は、配列をソートした上で「双方向ポインタ(Two Pointers)」というテクニックを使うことで効率的に解

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

    問題概要2つの整数 n と k が与えられたとき、1 から n までの数字の中から k 個を選んで作れるすべての組み合わせを求めます。例えば、n = 4、k = 2 の場合、答えは [[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]] となります。解法の考え方:バックトラッキングこの種の問題は、バックトラッキング(探索の巻き戻し)と呼ばれる手法で効率的に解くことができます。再帰関数を使って候補の数字を一つずつ選びながら組み合わせを構築し、条件を満たした時点で結果を保存していきます。アルゴリズムの手順再帰関数 solve() を用意します。引数は n、k、現在の組み合わせを