Pythonで海が見える建物を見つけるプログラム|単調スタックによるO(n)解法
問題の概要
さまざまな高さの建物のリストが与えられたとします。ある建物 heights[i] から海が見えるのは、その右側にあるすべての建物がその建物よりも低い場合です。この条件を満たす建物のインデックスを、昇順で求めるのがこの問題の目的です。
たとえば、入力が heights = [8, 12, 12, 9, 10, 6] の場合、出力は [2, 4, 5] になります。
- インデックス 2(高さ 12):右側の建物は 9・10・6 とすべて低いため、海が見えます。
- インデックス 4(高さ 10):右側には高さ 6 の建物しかなく、海が見えます。
- インデックス 5(高さ 6):右側に建物が存在しないため、必ず海が見えます。
一方、インデックス 1 の高さ 12 の建物は、同じく高さ 12 の建物(インデックス 2)が右側にあるため海は見えません。条件が「より低い」である点、「以下」ではない点に注意しましょう。
解法のアプローチ:単調スタック
この問題は単調スタック(monotonic stack)を使うと効率的に解けます。スタックには「現時点でまだ海が見える可能性のある建物のインデックス」だけを保持し、新しい建物がそれらを遮ったら取り除いていくイメージです。
具体的な手順は次のとおりです。
- 空のスタックを用意します。
- 各インデックス
idxと高さhについて、次を繰り返します。- スタックが空でなく、スタックの末尾のインデックスに対応する建物の高さが
h以下である間、スタックから要素を取り除きます(その建物は新しい建物に遮られ、海が見えなくなるため)。 idxをスタックに追加します。
- スタックが空でなく、スタックの末尾のインデックスに対応する建物の高さが
- 最後にスタックの中身を返します。
実装例(Python)
def solve(heights):
stack = []
for idx, h in enumerate(heights):
while stack and heights[stack[-1]] <= h:
stack.pop()
stack.append(idx)
return stack
heights = [8, 12, 12, 9, 10, 6]
print(solve(heights))
入力と出力
入力: [8, 12, 12, 9, 10, 6]
出力: [2, 4, 5]
動作のトレース
処理がどのように進むか、サンプルデータで確認してみましょう。
| idx | h | スタックへの操作 | スタックの状態 |
|---|---|---|---|
| 0 | 8 | スタックが空のため push | [0] |
| 1 | 12 | heights[0]=8 ≤ 12 なので pop → push | [1] |
| 2 | 12 | heights[1]=12 ≤ 12 なので pop → push | [2] |
| 3 | 9 | heights[2]=12 > 9 なので push のみ | [2, 3] |
| 4 | 10 | heights[3]=9 ≤ 10 なので pop → push | [2, 4] |
| 5 | 6 | heights[4]=10 > 6 なので push のみ | [2, 4, 5] |
最終的にスタックに残っている [2, 4, 5] が答えになります。
計算量
- 時間計算量:O(n) — 各建物は最大でも 1 回 push され、1 回 pop されるだけです。
- 空間計算量:O(n) — 最悪ケース(高さが降順に並んでいる場合)では、すべてのインデックスがスタックに残ります。
各建物ごとに右側をすべて調べる素朴な O(n²) の方法に比べ、単調スタックを使うことで線形時間で解けるのがこの手法の大きなメリットです。
-
Pythonで捕まえられる雨水の総量を計算するプログラム(トレッピング・レイン・ウォーター問題)
非負整数からなる長さ n の配列が与えられているとします。各要素はバーの高さを表し、それぞれのバーの幅は1です。このとき、雨が降った後に溜め込むことのできる水の総量を計算するのが本記事のテーマです。状況を図にすると、以下のようになります。図を見ると、水が溜まっている部分(青い箱)は全部で8個あります。したがって、このケースの出力は8となります。解法のアプローチこの問題は「スタック」を利用することで効率よく解けます。全体の手順は以下の通りです。スタック st、変数 water := 0、インデックス i := 0 を用意するi が高さ配列のサイズ未満である間、次の処理を繰り返すスタックが空である
-
Pythonで二分探索木(BST)の指定範囲内にあるノード数を求める方法
問題の概要 二分探索木(BST)が与えられ、さらに左側の境界値 l と右側の境界値 r が指定されます。このとき、木に含まれるすべてのノードの中で、値が l 以上 r 以下の範囲内にあるノードの個数を求めるのが目的です。 例えば、次のような木が与えられたとします。 このとき l = 7、r = 13 とすると、範囲内に含まれるノードは 8、10、12 の3つなので、出力は 3 になります。 アルゴリズムの考え方 スタックを使った反復的な深さ優先探索(DFS)で木をたどります。重要なポイントは、二分探索木の性質を活かして枝刈り(pruning)を行うことです。値が境界より小さいノードの左側の