Pythonで区間リストを1つの範囲につなぐための最小の挿入区間を求めるプログラム
数値の2次元リスト intervals が与えられ、その各行は [start, end](両端を含む)という区間を表しているものとします。区間 [a, b](a < b)のサイズは (b - a) で定義されます。ここで、このリストに区間を1つだけ追加し、すべての区間をマージした結果がちょうど1つの連続した範囲になるようにしたいと考えます。このとき、追加する区間のサイズとしてあり得る最小値を求めるのが本記事の目的です。
例として、入力が intervals = [[15, 20],[30, 50]] の場合を考えてみましょう。このとき出力は 10 になります。[20, 30] という区間を追加すれば、2つの区間がひと続きの範囲につながり、これ以上小さい区間では全体をカバーできないためです。
解決のアプローチ:イベントソートによるスイープ
この問題は、各区間の始点と終点を「イベント」として扱い、時系列順に走査する手法で効率的に解けます。アルゴリズムの手順は以下の通りです。
events:= 新しい空のリストintervals内の各区間から開始時刻 s と終了時刻 e を取り出し、次の処理を行う(s, 1)を events の末尾に追加(区間の開始イベント)(e, -1)を events の末尾に追加(区間の終了イベント)
- events をソートする
curr_status:= 0、last:= None で初期化interval:= ペア[0, 0]- events 内の各ペア (time, status) に対して次の処理を行う
- curr_status が 0 かつ last が存在し、time > last の場合(=まだ覆われていないギャップが存在する場合)
- interval[0] が 0 の場合、
interval[0] := last(最初のギャップの始点を記録) interval[1] := time(最後のギャップの終点を更新)
- interval[0] が 0 の場合、
last := timecurr_status := curr_status + status
- curr_status が 0 かつ last が存在し、time > last の場合(=まだ覆われていないギャップが存在する場合)
interval[1] - interval[0]を返す
このアルゴリズムのポイントは、curr_status が 0 に戻った瞬間に注目することです。ある時刻で被覆数が 0 になり、かつ直前の時刻より先に進んでいるなら、そこに「切れ目」が存在します。最初の切れ目の始点と、最後の切れ目の終点を結んだ区間こそが、すべてのギャップを一度に埋めて全体を1つの範囲にできる最小の区間となります。
実装例
それでは、実際のコードを見て理解を深めましょう。
class Solution:
def solve(self, intervals):
events = []
for s, e in intervals:
events.append((s, 1))
events.append((e, -1))
events.sort()
curr_status = 0
last = None
interval = [0, 0]
for time, status in events:
if curr_status == 0 and last and time > last:
if interval[0] == 0:
interval[0] = last
interval[1] = time
last = time
curr_status += status
return interval[1] - interval[0]
ob = Solution()
intervals = [[15, 20], [30, 50]]
print(ob.solve(intervals))
入力
[[15, 20],[30, 50]]
出力
10
計算量について
このアルゴリズムでは、n 個の区間から 2n 個のイベントを生成しソートするため、時間計算量は O(n log n)、イベントを格納するための空間計算量は O(n) となります。区間同士を総当たりで比較する方法(O(n²))よりも高速に動作するのが特徴です。
-
Pythonでソート済みリストの順序を保ったまま要素を挿入する2つの方法
本記事では、ソート済みのリストに対して、その並び順を崩すことなく新しい要素を挿入する方法について解説します。 問題文 リストが与えられたとき、既存のソート順を維持したまま、指定した要素を適切な位置に挿入する必要があります。 この問題を解くには、主に以下の2つのアプローチがあります。 アプローチ1:線形探索による力まかせ法(ブルートフォース) まず、挿入すべき位置をリストの先頭から順に走査して見つけ出し、そこへ要素を挿入するというシンプルな方法です。挿入する要素より大きい値が最初に現れた位置に、新しい要素を差し込みます。 コード例 n: index = i
-
Pythonでリストの累積和(累積合計)を求める方法
この記事では、リストの累積和(累積合計)を求める問題の解決策について詳しく解説します。問題文あるリストが与えられたとき、各要素までの累積和を格納した新しいリストを作成する必要があります。例えば、[10, 20, 30, 40, 50] というリストが与えられた場合、出力は [10, 30, 60, 100, 150] となります。これは、各位置でそれ以前の要素をすべて足し合わせた値です。実装例それでは、実際の実装を見ていきましょう。# 累積和を求める関数 def Cumulative(l): new = [] cumsum = 0 for element in l: