C++で配列の完全順列(撹乱順列)の数を求めるアルゴリズム
1からnまでの数字が昇順に並んだ長さnの配列があるとします。この配列から生成できる「完全順列(derangement:撹乱順列)」の総数を求めるのが本記事のテーマです。
組合せ数学において、完全順列とは、集合の要素を並べ替えた順列のうち、どの要素も元の位置に現れないようなものを指します。答えは非常に大きな数になる可能性があるため、109 + 7で割った余りを返すこととします。
例えば、入力が3の場合、出力は2になります。元の配列が [1, 2, 3] であるとき、条件を満たす完全順列は [2, 3, 1] と [3, 1, 2] の2通りだけだからです。
解法のアプローチ
この問題は動的計画法(DP)を用いて効率的に解けます。手順は以下の通りです。
- m := 109 + 7 と定義する
- 関数 add(a, b):((a mod m) + (b mod m)) mod m を返す
- 関数 mul(a, b):((a mod m) × (b mod m)) mod m を返す
- メイン処理では以下を実行する
- n が 1 の場合は 0 を返す(1つの要素を元の位置以外に置くことは不可能なため)
- n が 2 の場合は 1 を返す([2, 1] のみが有効)
- サイズ (n + 1) の配列 dp を用意する
- dp[2] := 1 と初期化する
- i を 3 から n まで増やしながら、dp[i] := mul(i − 1, add(dp[i − 2], dp[i − 1])) を計算する
- 最後に dp[n] を返す
漸化式のポイント
このアルゴリズムの中核となる漸化式は次の通りです。
dp[i] = (i − 1) × (dp[i − 1] + dp[i − 2])
この式が成り立つ理由は次の通りです。i番目の要素は、(i − 1) 個ある他の位置のいずれかに必ず移動します。移動先の要素 j がどこに移動するかで場合分けすると、j が i番目の位置に来るケース(残り i − 2 個の完全順列:dp[i − 2] 通り)と、そうでないケース(残り i − 1 個の完全順列:dp[i − 1] 通り)に分けられます。これらを合計して (i − 1) 倍することで、上記の漸化式が導かれます。
実装例
理解を深めるために、以下のC++による実装を見てみましょう。
#include <bits/stdc++.h>
using namespace std;
typedef long long int lli;
const lli m = 1e9 + 7;
lli add(lli a, lli b){
return ((a % m) + (b % m)) % m;
}
lli mul(lli a, lli b){
return ((a % m) * (b % m)) % m;
}
class Solution {
public:
int findDerangement(int n) {
int ret = 0;
if (n == 1)
return 0;
if (n == 2)
return 1;
vector<lli> dp(n + 1);
dp[2] = 1;
for (int i = 3; i <= n; i++) {
dp[i] = mul(i - 1, add(dp[i - 2], dp[i - 1]));
}
return dp[n];
}
};
main(){
Solution ob;
cout << (ob.findDerangement(3));
}
入力
3
出力
2
計算量について
このアルゴリズムの時間計算量は O(n)、空間計算量も O(n) です。dp 配列は直前の2つの値しか参照しないため、変数2つだけで管理すれば空間計算量を O(1) に削減することも可能です。
-
C++のSTLを使って配列の最大要素を見つける方法
この記事では、C++のSTL(標準テンプレートライブラリ)を使用して、配列の中から最大要素を見つける方法を解説します。例えば、配列が [12, 45, 74, 32, 66, 96, 21, 32, 27] の場合、最大要素は 96 となります。C++では、<algorithm> ヘッダーに用意されている max_element() 関数を使うことで、自分でループを書かずに最大要素を簡単に取得できます。この関数は、指定した範囲内の最大要素を指すイテレータを返すため、間接参照演算子(*)を使って実際の値を取り出します。サンプルコード#include<iostream> #
-
C++で配列内の数値の頻度(出現回数)を求める方法
配列に n 個の異なる要素が格納されているとします。この配列の中から、特定の要素が何回出現するか(頻度)を調べたい場合があります。例えば、配列 A = [5, 12, 26, 5, 3, 4, 15, 5, 8, 4] の中で「5」の頻度を調べると、答えは 3 になります。アルゴリズムの考え方この問題は、次の手順で解くことができます。1. 配列を左端から順に走査します。2. 現在の要素が調べたい数値と一致したら、カウンターを1つ増やします。3. 一致しない場合は、そのまま次の要素へ進みます。4. 配列の最後まで走査したら、カウンターの値が頻度となります。このアルゴリズムの計算量は O(n) で