コードカプリス合宿60日目| 84. ヒストグラムの最大の四角形

@TOC _


序文

コードランダムレコードアルゴリズムトレーニングキャンプ 60日目


1. Leetcode 84. ヒストグラム内の最大の長方形

1. トピック

n 個の負でない整数が指定され、ヒストグラムの各列の高さを表すために使用されます。各列は互いに隣接しており、幅は 1 です。

ヒストグラムで輪郭を描くことができる長方形の最大面積を見つけます。

例 1:

入力: heights = [2,1,5,6,2,3] 出力: 10 説明: 最大の長方形は画像内の赤い領域で、面積は 10 です。

例 2:

入力: 高さ = [2,4] 出力: 4

ヒント:

1 <= heights.length <=105
0 <= heights[i] <= 104

出典: LeetCode リンク: https://leetcode.cn/problems/largest-rectangle-in-histogram

2. 問題解決のアイデア

方法 1: 単調スタック

一連の考え

「高」を列挙する方法を要約しましょう。

首先我们枚举某一根柱子 ii 作为高 h=heights[i]h=heights[i];

随后我们需要进行向左右两边扩展,使得扩展到的柱子的高度均不小于 hh。换句话说,我们需要找到左右两侧最近的高度小于 hh 的柱子,这样这两根柱子之间(不包括其本身)的所有柱子高度均不小于 hh,并且就是 ii 能够扩展到的最远范围。

そこで、まず、列の高さよりも小さい、列の左側にある最も近い列を見つける方法を見てみましょう。「はしがき」に従って乱暴に列挙するだけでなく、次のような結論によって深く考えることができます。

对于两根柱子 j0j0​ 以及 j1j1​,如果 j0<j1j0​<j1​ 并且 heights[j0]≥heights[j1]heights[j0​]≥heights[j1​],那么对于任意的在它们之后出现的柱子 ii(j1<ij1​<i),j0j0​ 一定不会是 ii 左侧且最近的小于其高度的柱子。

言い換えると、2 つの柱 j0j0 と j1j1 があり、j0j0 が j1j1 の左側にあり、j0j0 の高さが j1j1 以上である場合、次の柱 ii が左に高さより低い柱を見つけると、 j1j1 が j0j0 を「ブロック」し、 j0j0 は答えになりません。

このようにして、いくつかの jj 値が昇順で格納されている「可能な答え」データ構造を維持しながら、配列を左から右にトラバースすることができます。上記の結論によれば、j0,j1,⋯,jsj0 ,j1 ,⋯,js を格納すると、height[j0] が存在する必要があります。

ii 番目の列まで列挙すると、ii 番目の列の左側と最も近い列がその列よりも小さい場合、 j0,j1,⋯,jsj0 ,j1 ,⋯,js がデータ構造に格納されます。身長がjijiなら、あるはずです

高さ[j0]

このように、二分探索法を使用して ii に対応する字を見つけることができますが、それは本当に必要でしょうか? i+1i+1 まで列挙すると、元の ii も jj の値になるので、ii がデータ構造に入れられます。データ構造内のすべての jj 値は ii より小さいため、高さが height[i]height[i] 以上であるすべての jj は答えではないため、データ構造から削除する必要があります。そして、これらの削除された jj 値がたまたま次であることがわかりました。

ji+1、⋯、jsji+1、⋯、js

このようにして、ii 番目の列まで列挙すると、まず高さが height[i]height[i] 以上であるすべての jj 値と、その中で最も高さが高い jj 値を削除できます。残りのjj値が答えです。その後、 ii をデータ構造に入れて、次の列挙を開始します。この時点で、使用する必要があるデータ構造、つまりスタックが完成します。

栈中存放了 jj 值。从栈底到栈顶,jj 的值严格单调递增,同时对应的高度值也严格单调递增;

当我们枚举到第 ii 根柱子时,我们从栈顶不断地移除 height[j]≥height[i]height[j]≥height[i] 的 jj 值。在移除完毕后,栈顶的 jj 值就一定满足 height[j]<height[i]height[j]<height[i],此时 jj 就是 ii 左侧且最近的小于其高度的柱子。
    这里会有一种特殊情况。如果我们移除了栈中所有的 jj 值,那就说明 ii 左侧所有柱子的高度都大于 height[i]height[i],那么我们可以认为 ii 左侧且最近的小于其高度的柱子在位置 j=−1j=−1,它是一根「虚拟」的、高度无限低的柱子。这样的定义不会对我们的答案产生任何的影响,我们也称这根「虚拟」的柱子为「哨兵」。

我们再将 ii 放入栈顶。

スタックに格納される要素は単調であり、これが古典的なデータ構造「単調スタック」です。

読者が単調スタックを理解できるように、特定の例 [6,7,5,2,4,5,9,3][6,7,5,2,4,5,9,3] を使用します。各列の左側と、その高さより小さい最も近い列を見つける必要があります。最初はスタックは空です。

我们枚举 66,因为栈为空,所以 66 左侧的柱子是「哨兵」,位置为 -1。随后我们将 66 入栈。
    栈:[6(0)]。(这里括号内的数字表示柱子在原数组中的位置)

我们枚举 77,由于 6<76<7,因此不会移除栈顶元素,所以 77 左侧的柱子是 66,位置为 00。随后我们将 77 入栈。
    栈:[6(0), 7(1)]

我们枚举 55,由于 7≥57≥5,因此移除栈顶元素 77。同样地,6≥56≥5,再移除栈顶元素 66。此时栈为空,所以 55 左侧的柱子是「哨兵」,位置为 −1−1。随后我们将 55 入栈。
    栈:[5(2)]

接下来的枚举过程也大同小异。我们枚举 22,移除栈顶元素 55,得到 22 左侧的柱子是「哨兵」,位置为 −1−1。将 22 入栈。
    栈:[2(3)]

我们枚举 44,55 和 99,都不会移除任何栈顶元素,得到它们左侧的柱子分别是 22,44 和 55,位置分别为 33,44 和 55。将它们入栈。
    栈:[2(3), 4(4), 5(5), 9(6)]

我们枚举 33,依次移除栈顶元素 99,55 和 44,得到 33 左侧的柱子是 22,位置为 33。将 33 入栈。
    栈:[2(3), 3(7)]

このようにして、それらの左側の列番号を [−1,0,−1,−1,3,4,5,3][−1,0,−1,−1,3,4 として取得します。 ,5,3]。同じ方法を使用して、右から左にトラバースし、その右側の列番号を [2,2,3,8,7,7,7,8][2,2,3, 8, 7,7,7,8] のように、ここでは位置 88 を「センチネル」とみなします。

左側と右側の列を取得したら、各列に対応する左右の境界を計算し、答えを見つけることができます。

分析する

単調スタックの時間計算量はどれくらいですか? 直接計算するのは非常に困難ですが、次のことがわかります。

每一个位置只会入栈一次(在枚举到它时),并且最多出栈一次。

したがって、配列を左から右へ、または合計で右から左へトラバースすると、スタック上の操作の数は O(N)O(N) になります。したがって、単調スタックの合計時間計算量は O(N)O(N) になります。

3. コードの実装

```java クラス Solution { public int biggestRectangleArea(int[] heights) { int n = heights.length; int[] left = 新しい int[n]; int[] right = 新しい int[n];

Deque<Integer> mono_stack = new ArrayDeque<Integer>();
    for (int i = 0; i < n; ++i) {
        while (!mono_stack.isEmpty() && heights[mono_stack.peek()] >= heights[i]) {
            mono_stack.pop();
        }
        left[i] = (mono_stack.isEmpty() ? -1 : mono_stack.peek());
        mono_stack.push(i);
    }

    mono_stack.clear();
    for (int i = n - 1; i >= 0; --i) {
        while (!mono_stack.isEmpty() && heights[mono_stack.peek()] >= heights[i]) {
            mono_stack.pop();
        }
        right[i] = (mono_stack.isEmpty() ? n : mono_stack.peek());
        mono_stack.push(i);
    }

    int ans = 0;
    for (int i = 0; i < n; ++i) {
        ans = Math.max(ans, (right[i] - left[i] - 1) * heights[i]);
    }
    return ans;
}

}

おすすめ

転載: blog.csdn.net/HHX_01/article/details/131285451