C++で互いに素な区間の最大集合を求めるアルゴリズム
概要
N個の区間(インターバル)が与えられたとき、互いに素な(重ならない)区間の最大集合を見つけることが本記事の課題です。2つの区間 [i, j] と [k, l] は、共通の点を1つも持たない場合に「互いに素(disjoint)」であると定義されます。
例えば、区間が {{10, 20}, {23, 35}, {15, 21}, {37, 41}} である場合、重なりなく選択できる最大の組み合わせは以下の通りです。
{10, 20}
{23, 35}
{37, 41}ここで注意したいのは、{15, 21} を含めることができないという点です。{15, 21} は {10, 20} と重なってしまうためです。
アルゴリズム(貪欲法)
この問題は「活動時間割り当て問題」としても知られており、貪欲法(Greedy Algorithm)によって効率的に解くことができます。手順は以下の通りです。
- 各区間の終点を基準にして、すべての区間をソートします。
- すべての区間を先頭から順に走査し、直前に採用した区間と重なっている場合は、終点がより小さい方の区間を採用します。終点が小さい区間を選ぶことで、その後に続く区間をできるだけ多く、重なりなく受け入れられるようになります。
- この手順をすべての区間に対して繰り返し、条件を満たす区間を出力します。
C++による実装例
#include <bits/stdc++.h>
using namespace std;
bool sortFun(pair<int, int> &a, pair<int, int> &b){
return (a.second < b.second);
}
void getMaxDisjointInterval(vector<pair<int, int>> intervals){
sort(intervals.begin(), intervals.end(), sortFun);
cout << "{" << intervals[0].first << ", " << intervals[0].second << "}\n";
int r1 = intervals[0].second;
for (int i = 1; i < intervals.size(); ++i) {
int l1 = intervals[i].first;
int r2 = intervals[i].second;
if (l1 > r1) {
cout << "{" << l1 << ", " << r2 << "}\n";
r1 = r2;
}
}
}
int main(){
int n = 4;
vector<pair<int, int>> intervals = {
{10, 20},
{23, 35},
{15, 21},
{37, 41}
};
cout << "Max disjoint pairs are:\n";
getMaxDisjointInterval(intervals);
return 0;
}コードのポイント
sortFun関数は、各pairの2番目の要素(区間の終点)を比較することで、区間を終点順に並べ替えます。getMaxDisjointInterval関数では、最初の区間を出力した後、変数r1に現在採用中の区間の終点を保持します。- 以降の区間については、始点
l1がr1より大きい場合のみ出力し、r1を更新していきます。これにより重なりを確実に回避できます。
出力結果
上記のプログラムをコンパイルして実行すると、次の出力が得られます。
Max disjoint pairs are:
{10, 20}
{23, 35}
{37, 41}計算量について
このアルゴリズムの計算量は、区間のソートに O(N log N)、走査に O(N) かかるため、全体として O(N log N) となります。区間スケジューリング問題の中でも非常に効率的な解法であり、実務上のスケジュール最適化やリソース割り当ての問題にも応用できます。
-
C++で点集合の線対称(ラインリフレクション)を判定するアルゴリズム
問題概要2次元平面上にn個の点が与えられます。このとき、y軸に平行な直線で全ての点を鏡映(反射)した結果が、元の点集合と完全に一致するような直線が存在するかどうかを判定します。言い換えれば、ある直線を対称軸として全ての点を反転させたとき、反転後の点の集合が元の集合と同一になるかを確認する問題です。例えば、入力が points = [[1,1],[-1,1]] の場合を考えてみましょう。この場合、x = 0 の直線(y軸)を対称軸とすると、点 (1,1) は (-1,1) へ、(-1,1) は (1,1) へと移ります。点集合全体としては変化がないため、出力は true となります。解法のポイン
-
C++で解く対角トラバースII:リストのリストを対角順に出力する方法
問題の概要 「リストのリスト」である nums が与えられたとき、そのすべての要素を対角順(ダイアゴナルオーダー)に並べて出力するのがこの問題の目的です。 たとえば、次のような行ごとに長さの異なる配列(ジャグ配列)が入力として与えられた場合を考えてみましょう。 このとき、期待される出力は次のとおりです。 [1, 6, 2, 8, 7, 3, 9, 4, 12, 10, 5, 13, 11, 14, 15, 16] 解法のアプローチ この問題は、各要素を「値と座標のセット」として一旦記録し、対角線ごとの順序になるようにソートし直すことで解けます。具体的な手順は以下の通りです。 結果を格納す