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

【C++】重複する要素を含む配列の順列をすべて求める方法(Permutations II)

問題の概要

整数のコレクションが与えられたとき、そのすべての順列(並べ替えのパターン)を求めることを考えます。ただし、配列に重複した要素が含まれている場合は、見た目が同じ順列は結果から除外する必要があります。

例えば、配列が [1, 1, 3] の場合、期待される出力は次のようになります。

[[1,1,3], [1,3,1], [3,1,1]]

解法のアプローチ:バックトラッキング(再帰)

この問題は、再帰的なバックトラッキングを使うことで効率的に解くことができます。基本的な手順は以下の通りです。

  1. リストとインデックス idx を引数に取る再帰関数を用意します。最初は idx = 0 から開始します。
  2. idx がリストのサイズと等しくなったら、その時点のリストを結果配列 res に追加して処理を終了します。
  3. iidx からリストの末尾までループさせます。
    • nums[i] == nums[idx] かつ i != idx の場合は、同じ順列が生成されるため、その要素はスキップします。
    • nums[i]nums[idx] を入れ替えます。
    • solve(nums, idx + 1) を呼び出して再帰的に処理を続けます。
  4. 事前に配列をソートしておくことで、重複チェックを正しく機能させます。最後に res を返します。

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(vector <int> nums, int idx = 0){
        if(idx == nums.size()){
            res.push_back(nums);
            return;
        }
        for(int i = idx; i <nums.size(); i++){
            if(nums[i] == nums[idx] && i != idx)continue;
            swap(nums[i], nums[idx]);
            solve(nums, idx + 1);
        }
    }
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        res.clear();
        sort(nums.begin(), nums.end());
        solve(nums);
        return res;
    }
};

main(){
    Solution ob;
    vector<int> v = {1,1,3};
    print_vector(ob.permuteUnique(v));
}

実行結果

入力

[1,1,3]

出力

[[1,1,3],[1,3,1],[3,1,1]]

ポイントの解説

  • ソートの重要性: permuteUnique() 内で sort() を呼び出しているのは、同じ値の要素を隣接させるためです。これにより、重複チェックの条件 nums[i] == nums[idx] が正しく機能します。
  • 重複のスキップ: 同じ位置に同じ値が来るパターンは既に探索済みのため、continue でスキップすることで無駄な計算を省き、重複した順列が結果に含まれるのを防いでいます。
  • 計算量: 要素数を n とすると、最悪の場合の時間計算量は O(n × n!) となります。重複が多い配列では、スキップによって実際の探索数は大幅に削減されます。
  1. C++で解くリスのナッツ収集シミュレーション ― 最小移動距離を求めるアルゴリズム

    問題概要 1本の木、1匹のリス、そして複数のナッツがフィールド上にあります。それぞれの位置は2次元グリッドのセルで表現されます。この問題の目的は、リスがすべてのナッツを集めて木の下に1個ずつ運ぶときの最小移動距離を求めることです。 リスの行動には次の制約があります。 一度に持てるナッツは最大1個 移動は上下左右の4方向で、隣接するセルへのみ可能 距離は移動回数(ステップ数)で表される たとえば、入力が「高さ: 5 / 幅: 7 / 木の位置: [2,2] / リスの位置: [4,4] / ナッツ: [[3,0], [2,5]]」の場合、出力は 12 となります。 解法のポイント まず、

  2. C++で解く長方形エリアII ― 座標圧縮と走査線法による被覆面積の計算

    問題概要 軸に平行な長方形のリストが与えられるものとします。各 rectangle[i] = {x1, y1, x2, y2} において、(x1, y1) は i 番目の長方形の左下隅の座標、(x2, y2) は右上隅の座標を表します。 求めたいのは、平面上でこれらすべての長方形が覆っている領域の合計面積です。答えは非常に大きな値になる可能性があるため、109 + 7 で割った余りを返すことになっています。 たとえば、入力が次のような場合を考えてみましょう。 このとき、出力は 6 となります。 解法の方針:座標圧縮+走査線(スイープライン) この問題は、座標圧縮(座標の離散化)と走査線法(