Algorithm Learning (Discipleship Program) 4-2 Monotone-Stack (モノトーンスタック) と古典問題学習ノート
- 序文
- 単調スタック
- 古典的な例
-
- LeetCode 155. 最小限のスタック (基本)
- LeetCode 496. 次の大きな要素 I (モノトニック スタック 1 の一般的な適用)
- LeetCode 503. 次の大きな要素 II (モノトニック スタック 2 の一般的な適用)
- LeetCode 901. 株価スパン (単調スタック 3 の共通アプリケーション)
- LeetCode 739。毎日の気温
- LeetCode 84. ヒストグラムの最大の長方形 (最も適切なモノトニック スタックの例)
- LeetCode 42. 雨水をつかむ
- LeetCode 456.132 パターン
- LeetCode 907. サブアレイの最小値の合計 (RMQ と影響間隔)
- LeetCode 1856。部分配列の最小積の最大値
- エピローグ
序文
(7.21、大成功)
(今回も最短の学習時間に挑戦、3 倍の時間がかかり、13H 以内)
(核となるアイデアは、詳細かつ適切であり、要点を把握することです)
この記事は、クラスの開始、Discipleship Project 4-2 Monotone-Stack (モノトーン スタック) の講義 11 と古典的な問題 (
ビデオは、第 6 章、セクション 2: 5-2 Monotone-Stack (モノトーン) の主題です) -スタック) と古典的な問題)
このレッスンは前のレッスンと同じくらい抽象的です。最初に単調さの概念を復習しましょう。単調とは、ストレージ構造において、値が増加または減少する概念で配置されることを意味します. 前回のレッスンはキューの形で配置されていましたが、今回はスタックを使用するように変更されています。
この授業研究の目的は次のとおりです。
- モノトニック スタックの性質とアプリケーション シナリオを理解する
学習概要(学習後の記録):
- モノトニックスタックはデキュー機能をキャンセルしたモノトニックキューであり、モノトニックスタックは間隔の最大値に注意を払いません
- 単調スタックとは、新要素が旧要素列をどのように1つに分割するかに注目し、新要素の前の値または次の値に注目することです。
- 単調スタックは、値の間隔スパンを見つけるためによく使用されます(特定の値の後にどれだけ大きい値または小さい値が表示されるか)
- モノトニック スタックから要素をポップする方法は、スタックの一番上からのみポップされます。
- (余分な知識) アルゴリズムの質問は、思考パターンを練習するためのものです。
(特記事項: モノトニック スタックはモノトニック キューの特殊な使用法であるため、問題の解決策を説明するためにモノトニック キューを使用することにまだ慣れていることがありますが、それをモノトニック スタックに置き換えることは同じです)
単調スタック
ベース
単調キューの簡単な比較
- キューに入るときに単調性を維持して、キューの最初の要素が常に最も価値のある区間要素になるようにする
- 最大値を維持する場合は降順キューを使用し、最小値を維持する場合はその逆を使用する必要があります。
単調キューとスタックの関係
- チームに入るときの単調性の維持はチームの最後に行われ、チームの先頭からチームを離れることを許可されない場合、全体は単調なスタック構造になります
単調スタックの簡単な理解
- モノトニック スタックは、最初のキューからデータを出力できないモノトニック キューです (スタック構造と単調性)。
- キューの先頭から要素を取り出す必要がなく、キューの最初の要素を取得する必要がないため、したがって、モノトニックスタックはスタックデータ構造を使用できます。
自然
考慮すべき質問:
- スタックにプッシュするプロセスで、どの要素が残され、どの要素が後続の要素によって拒否されて削除されるか
抽象化
この時点で単調性が増していると仮定すると、振動シーケンスの場合、水平線を下から上に上げ、水平線が要素に触れたときに、この要素を記録し、要素の左端で水平線を切り取ります、現時点では、すべて記録された要素は出現順に残されます、すべてのレコード要素間の要素のような後続の要素によって覆されます。排除される
思考をさらに一般化する
スタックにプッシュされた各要素は、要素をスタック内の要素の 2 つの部分に分割します (インクリメントされると想定されます)。
- 影響を受けない要素 (新しい要素はこの要素の最大値であり、全体の単調性に準拠します)
- 削除された要素 (新しい要素はこの要素の最小値であり、全体は単調性に従います)
(全体的に単調性に準拠しているのは、スタック内の構造自体が単調性に準拠しているためです。そのため、任意のセグメントを傍受することは単調性と一致しています)
新しい要素はスタック内の前の要素よりも大きいが、削除された前の要素よりも小さいことがわかります。
(要件に応じて対等な状況を検討)
コード
(定義がここに等しい場合、スタックはポップされません)
(以下のコードは、前のレッスンで単調なキューを使用して原理を実現できますが、学習用であるため、今回はスタックで解決します)
(以下のコードのスタック内の要素は、元の要素の添字です)
Stack<Integer> stack = new Stack<>();
for(int i= 0;i<nums.length;i++){
while(stack.size()>0){
Integer top = stack.peek();
if(nums[top]<nums[i]){
stack.pop();
//todo
continue;
}
break;
}
stack.push(i);
}
要約する
- 単調スタックは単調待ち行列の特殊な応用形態であり、理解を容易にするためにスタック構造で表されます。抽象的な概念
- 単調スタックは、より多くの用途に使用されます新しい要素と古い要素の相互作用に注目する、つまり、スタックにプッシュされたときに古い要素が新しい要素によってどのように2つに分割され、一部が残り、一部が削除されるかに注意してください。
- モノトニック スタックから要素を取得する方法は、スタックの一番上 (モノトニック キューの末尾) からのみスタックをポップすることです。
古典的な例
LeetCode 155. 最小限のスタック (基本)
リンク: https://leetcode-cn.com/problems/min-stack
プッシュ、ポップ、トップ操作をサポートし、最小の要素を一定時間で取得できるスタックを設計します。
push(x) – 要素 x をスタックにプッシュします。
pop() - スタックの一番上にある要素を削除します。
top() - スタックの一番上の要素を取得します。
getMin() - スタック内の最小要素を取得します。
問題解決のアイデア
単調な待ち行列を学んだ後、この問題は難しくありません.この問題に非常に似た最後のクラスの問題がありましたが、データ構造は待ち行列として表され、この問題はスタックに変更されました.
ただし、最も価値のあるものを格納するという目標は変わっていないため、最も価値のある部分は単調なキューで行われます。具体的な操作は次のとおりです。
- スタックにプッシュすると、スタックにプッシュされ、単調なキューもエンキューされます
- スタックがポップされると、判定行の終わり要素をデキューする必要があるかどうか
- スタックの一番上を取得し、スタックの一番上から要素を取得します
- 最大値を取得するときは、モノトニック キューから先頭要素を取得します
(コード省略)
(ここで、モノトニック スタックの特徴は、スタックの一番上、つまりモノトニック キューの末尾からスタックをポップすることであることを明確にする必要があります)
LeetCode 496. 次の大きな要素 I (モノトニック スタック 1 の一般的な適用)
リンク: https://leetcode-cn.com/problems/next-greater-element-i
重複する要素のない 2 つの配列 nums1 と nums2 が与えられます。ここで、nums1 は nums2 のサブセットです。
nums2 の nums1 の各要素の次に大きい値を見つけてください。
nums1 の数値 x の次に大きい要素は、nums2 の x の対応する位置の右側にある x より大きい最初の要素です。存在しない場合は、対応する位置に -1 を出力します。
例 1:
输入: nums1 = [4,1,2], nums2 = [1,3,4,2].
输出: [-1,3,-1]
解释:
对于 num1 中的数字 4 ,你无法在第二个数组中找到下一个更大的数字,因此输出 -1 。
对于 num1 中的数字 1 ,第二个数组中数字1右边的下一个较大数字是 3 。
对于 num1 中的数字 2 ,第二个数组中没有下一个更大的数字,因此输出 -1 。
問題解決のアイデア
この質問の核心は、nums2 の nums1 の各要素の次に大きい値を見つけてください。
(ここでの次のものは、比較値よりも大きい右側の最初の数を指すことに注意してください)
これは、モノトニック スタックの考え方と一致しています。モノトニック スタックとは、新しい要素が古い要素シーケンスを 1 つに分割する方法に注目し、新しい値の前の値または次の値に注目することです。
この質問では、nums2 は古い要素の概念を形成し、nums1 は新しい要素です. nums1 は nums2 から派生しているため、期待値よりも大きい最初の数を見つけることが期待されるため、減少単調キューを使用できます.新しく追加された要素によって削除できる要素がたまたまnum1の要素である場合、新しく追加された要素は対応するnum1要素に期待されます次。
この質問では、モノトニック スタックを使用してアイデアを説明しています。減少するモノトニック スタックを設計しますが、要素がスタックにプッシュされたときに要素がポップアップすると、num1 の要素が含まれると、変更された要素は nums2 の次の要素になります。 num1.value の対応する要素によって要求されます。
(コードを書いて学ぶ)
サンプルコード
class Solution {
public int[] nextGreaterElement(int[] nums1, int[] nums2) {
HashMap<Integer,Integer> h = new HashMap<Integer,Integer> ();
for(int i = 0;i<nums1.length;i++){
h.put(nums1[i],-1);
}
Stack<Integer> stack = new Stack<>();
for(int i= 0;i<nums2.length;i++){
while(stack.size()>0){
Integer top = stack.peek();
if(top<nums2[i]){
stack.pop();
if(h.get(top)!=null){
h.put(top,nums2[i]);
}
continue;
}
break;
}
stack.push(nums2[i]);
}
for(int i = 0;i<nums1.length;i++){
nums1[i]= h.get(nums1[i]);
}
return nums1;
}
}
LeetCode 503. 次の大きな要素 II (モノトニック スタック 2 の一般的な適用)
リンク: https://leetcode-cn.com/problems/next-greater-element-ii
循環配列 (配列の最初の要素の次の最後の要素) を指定すると、各要素の次に大きい要素を出力します。数値 x の次の大きい要素は、配列トラバーサル順序でこの数値の後にある最初の大きい数値です。つまり、次の大きい数値を検索するためにループする必要があります。存在しない場合は -1 を出力します。
例 1:
入力: [1,2,1]
出力: [2,-1,2]
説明: 最初の 1 の次に大きい数は 2 です;
数 2 は次に大きい数を見つけることができません; 2
番目は 1 の次に大きい数です循環的に検索する必要があり、結果も 2 です。
注: 入力配列の長さは 10000 を超えません。
問題解決のアイデア
この問題は前の質問と似ています. 次に大きい数を逆算するためのものなので, 単調に減少するスタックを設計します. ある要素がスタックから飛び出せるとき, スタックから飛び出せる要素を出力します.結果セットに。
したがって、設計コードは、結果セットを初期化し、すべての要素を -1 にしてから、モノトーン スタックを使用してすべての要素をスタックにプッシュし、スタックから出たすべての要素を結果セットに入れます。結果セットに回答がある場合、新しく生成された回答は受け入れられなくなります (繰り返しトラバーサルの影響を防ぐため)。
(コード省略)
LeetCode 901. 株価スパン (単調スタック 3 の共通アプリケーション)
リンク: https://leetcode-cn.com/problems/online-stock-span
ある株の毎日の相場を収集し、その日のその株の価格のスパンを返す StockSpanner クラスを作成します。
今日の株価のスパンは、株価が今日の価格以下であった最大連続日数 (今日を含めて今日からさかのぼる) として定義されます。
たとえば、次の 7 日間の株価が [100, 80, 60, 70, 60, 75, 85] の場合、在庫期間は [1, 1, 1, 2, 1, 4, 6]。
問題解決のアイデア
理解する必要がある概念は、スパンとは (今日を含めて今日から逆算して) 株価が今日の価格以下である連続した最大日数であるということです。
したがって、特定の新しい要素には、前のシーケンス以下の数の数があることがわかります.この要件は、単調スタックを使用して解決でき、単調減少スタックを使用する必要があり、等しい状況も必要です.ポップされる
(この質問の設計では、スタック内の要素は元の要素の添字として格納する必要があります。この方法は、データの次元を増やして、少量のデータ空間でより多くの情報を表現できるようにする方法です。この前のレッスンの追加知識です )
(この質問を簡単に行ってから、手を練習してください)
サンプルコード
(27ms,46.3MB,ナイス)
class StockSpanner {
Stack<Integer> stack ;
ArrayList<Integer> priceList;
public StockSpanner() {
stack = new Stack<>();
priceList = new ArrayList<Integer>();
}
public int next(int price) {
priceList.add(price);
int over = 1;
Integer top = null;
while(stack.size()>0){
top = stack.peek();
if(priceList.get(top)<=price){
stack.pop();
continue;
}
break;
}
if(stack.size()>0)
top = stack.peek();
else
top = -1;
over = priceList.size()-top-1;
stack.push(priceList.size()-1);
return over;
}
}
/**
* Your StockSpanner object will be instantiated and called as such:
* StockSpanner obj = new StockSpanner();
* int param_1 = obj.next(price);
*/
LeetCode 739。毎日の気温
リンク: https://leetcode-cn.com/problems/daily-temperatures
毎日の温度リストの温度に従って、毎日の温度が高くなるまで何日待つ必要があるかを計算してください。その後も温度が上がらない場合は、ここに 0 を代入してください。
例 1:
入力: 温度 = [73,74,75,71,69,72,76,73]
出力: [1,1,4,2,1,1,0,0]
問題解決のアイデア
この質問は、気温シリーズの場合、新しい要素がスタックにプッシュされたときに、スタックからポップされた要素とその最大スパン (最後にポップされた要素の添え字) として理解できます。
この質問は上位に記述されているため、同等の状況はスタックから離れません。
(非常に単純なコードは省略)
LeetCode 84. ヒストグラムの最大の長方形 (最も適切なモノトニック スタックの例)
リンク: https://leetcode-cn.com/problems/largest-rectangle-in-histogram
n 個の非負の整数を指定すると、ヒストグラムの各列の高さを表すために使用されます。各列は互いに隣接しており、幅は 1 です。
ヒストグラムで概説できる長方形の最大面積を見つけます。
例 1:
入力: heights = [2,1,5,6,2,3]
出力: 10
説明: 最大の長方形は図の赤い領域で、面積は 10 です
問題解決のアイデア
この質問は前の質問と似ています: 木の板で額装できる最大容量は非常に近いですが、特別な制限があります. 長方形領域の高さは、最も短い長方形によって決まります.
したがって、この質問は変換できます任意の間隔で最小値を見つけるにはとなり、間隔幅に応じて計算し、列挙によって最大値を見つけます。
(これは基本的な解決策であり、実行可能でなければならないので、それを最適化するアイデアはありますか?)
(はい、それは間隔に対する要素の影響であり、私は影響間隔と呼んでいます)
したがって、この質問を変換して、各要素が影響を与える可能性のある最大間隔範囲 (たとえば、シーケンス内の最小値、影響を受ける間隔はシーケンス全体) を、この間隔内の最小値として見つけることができます。
さらなる変換は、要素の左側で最初に小さい値を探し、要素の右側で最初に小さい値を探し、座標を記録することと理解できます。
この通常、モノトニック スタックで実装される、より小さい (または大きい) 最初の要件を見つけます。、したがって、この質問は、元のシーケンスの左側と右側からそれぞれスタックにプッシュされる 2 つの単調に増加するスタック (小さな値を探す) を設計し、各要素がポップアウトされるときに影響を受ける要素の添え字を記録できます。それを配列に格納し、最後にこの配列の要素間の差 (最初の右の小さい方の座標から最初の左の小さい方の座標を引いたもの) を使用して、各要素の影響間隔 (ri-li) を計算します。 - 1)、生成できる長方形の面積を計算できるようにします。
(最適化の余地はあるかもしれませんが、学習の観点からは十分です)
(授業を聞いた後、私はさらに最適化の余地があることに気づきました。つまり、要素の影響間隔を計算する方法です) に
変更:
単調増加スタックを使用し、要素がスタックに置かれると、この要素の前の要素はその範囲の左側になり、要素がスタックからポップされると、ポップされる要素はその範囲の右側になります
(現在のコースの知識を完全に把握していないため、この解決策は期待していませんでした。単調なスタックから飛び出した要素のみを考慮し、スタック内の要素間の関係を忘れています)
サンプルコード
class Solution {
public int largestRectangleArea(int[] heights) {
int wights [] = new int [heights.length];
Stack<Integer> stack = new Stack<Integer> ();
for(int i= 0;i<heights.length;i++){
while(stack.size()>0){
Integer top = stack.peek();
if(heights[top]>heights[i]){
wights [top] = i- wights [top]-1;
//System.out.println(top+"+"+ wights [top]);
stack.pop();
continue;
}
break;
}
if(stack.size()>0)
wights [i] =stack.peek();
else
wights [i] = -1;
stack.push(i);
}
while(stack.size()>0){
Integer top = stack.pop();
if(stack.size()>0)
wights [top] = heights.length-stack.peek()-1;
else
wights [top] = heights.length;
//System.out.println(top+"+"+ wights [top]);
}
int max = 0;
for(int i= 0;i<heights.length;i++){
int s = wights[i]*heights[i];
if(s>max)
max = s;
}
return max;
}
}
LeetCode 42. 雨水をつかむ
リンク: https://leetcode-cn.com/problems/trapping-rain-water
幅が 1 の各柱の高さマップを表す n 個の非負の整数が与えられた場合、このように配置された柱が雨が降った後にどれだけの雨水を受け取ることができるかを計算します。
例 1:
入力: 高さ = [0,1,0,2,1,0,1,3,2,1,2,1]
出力: 6
説明: 上記は配列 [0,1,0,2, 1,0 ,1,3,2,1,2,1] は高さマップを表し、この場合、6 単位の雨水を受け取ることができます (青い部分は雨水を表します)。
問題解決のアイデア
この問題は、どのくらいの水を受け取ることができるかを計算するもので、水を受ける間隔は、両側の最高値と中間値が占めるスペースによって決まります。
そして想像してみてください、この質問には 2 つのピークがありますか? いいえ、2 つのピークが水で満たされる可能性があるため、この質問ではシーケンス内の最大値を見つけてから、左右の最大値を見つけて保存するだけでよく、これまでのところ最初のグループの結果が生成されています。
次に、最初の一連の結果に従って、内側への注水を判断します。
(コードを最適化できます)
この質問は区間の両側から開始し、各区間の最大値を内側に見て、同時に注水を判断し、左区間の注水と右区間の注水として記録するように変更されます。水を注入するときはすぐに水を注入するのではなく、とりあえず水を注入して記録し、その結果を試して、接近する過程でより大きな値が見つかった場合は、試行結果を受け入れ、左右の試行まで接近し続ける場合は、その結果を受け入れます。間隔が満たされると、短い方の間隔が引き続き試行され、長い方の間隔は破棄されます。
(このソリューションの利点は、一定レベルのスペースしか必要とせず、時間効率が向上する可能性があることです)
(ただ学ぶだけなら最初の解しか理解できないが、より高度な技術を求めるなら、習得した知識の枠組みから飛び出して、現象から問題を解かなければならない。現時点では、それだけではない。学習、しかし革新)
(プラン 2 を完了するのに約 1 時間かかりましたが、それだけの価値はありますか?)
(したがって、モノトニックスタックの使用方法を検討する場合)
オプション3:
単調減少スタックを使用して、要素がスタックに置かれるとき、前の要素がそれよりも大きくなければならないか、そのような要素が存在しない場合、削除された要素はそれよりも小さい要素であり、削除された要素は容量を形成します。
そして、これらの容量はレイヤーごとにあり、容量が正常に計算されるたびに、結果に直接保存できます。
(計画 3 を完了しました。30 分しかかかりませんでした。これが知識の価値であり、人生の 30 分です)
サンプルコード
(シナリオ 2)
(1ms、38.1MB)
class Solution {
public int trap(int[] height) {
int lp = 0,rp = height.length-1;
int res = 0,resL = 0,resR = 0;
int hL =0;
int hR = 0;
int prel= lp ,prer=rp ;
for(;lp<=rp;lp++,rp--){
if(height[lp]>=hL){
res+= resL;
resL = 0;
prel = lp;
hL = height[lp];
}else{
resL += hL-height[lp];
}
if(height[rp]>=hR){
res+= resR;
resR = 0;
prer = rp;
hR = height[rp];
}else{
resR += hR-height[rp];
}
}
//System.out.println(lp+"--"+rp+".."+res);
if(hL>hR){
for(;rp>prel;rp--){
if(height[rp]>=hR){
res+= resR;
resR = 0;
hR = height[rp];
}else{
resR += hR-height[rp];
}
}
res+= resR;
}else{
for(;lp< prer;lp++){
//System.out.println(resL);
if(height[lp]>=hL){
res+= resL;
resL = 0;
hL = height[lp];
}else{
resL += hL-height[lp];
}
}
res+= resL;
}
return res;
}
}
(Scheme 3: Monotonic stack)
(2ms, 38.2MB, 性能はやや劣るがコードは読みやすく開発に適している)
class Solution {
public int trap(int[] height) {
int res = 0;
Stack<Integer> stack = new Stack<Integer> ();
int max =0;
for(int i= 0;i<height.length;i++){
while(stack.size()>0){
Integer top = stack.peek();
if(height[top]<height[i]){
stack.pop();
if(stack.size()>0){
int prei = stack.peek();
if( max<height[i])
res+= (top - prei)*(max-height[top]);
else
res+= (top - prei)*(height[i]-height[top]);
}
continue;
}
break;
}
if( max<height[i]) max = height[i];
stack.push(i);
}
return res;
}
}
LeetCode 456.132 パターン
リンク: https://leetcode-cn.com/problems/132-pattern
整数配列 nums を指定すると、配列には n 個の整数があります。132 パターンのサブシーケンスは、3 つの整数 nums[i]、nums[j]、および nums[k] で構成され、i < j < k および nums[i] < nums[k] < nums[j] を同時に満たします。
パターン 132 のサブシーケンスが nums に存在する場合は true、そうでない場合は false を返します。
例 3:
入力: nums = [-1,3,2,0]
出力: true
説明: シーケンスには 132 パターンの 3 つのサブシーケンスがあります: [-1, 3, 2]、[-1, 3, 0] および [ - 1、2、0] .
問題解決のアイデア
今回の質問で把握する必要がある132モードとは、2つの要素を持つ成長シーケンスを意味します.このとき、3番目の要素を持たせようとすると、これら2つの要素の間に新しい要素を挿入する必要があることがわかります.
この質問は増分キューを設計できるようで、要素がキューに入ると、新しい要素が古い要素をキューから出すように見え、新しい要素の後に古いキューに少なくとも2つの要素がある場合行列に入る。
(コード省略)
LeetCode 907. サブアレイの最小値の合計 (RMQ と影響間隔)
リンク: https://leetcode-cn.com/problems/sum-of-subarray-minimums
整数配列 arr を指定して、min(b) の合計を見つけます。ここで、b は arr の各 (連続した) サブ配列にまたがっています。
答えは大きくなる可能性があるため、10^9 + 7 を法として答えを返します。
例 1:
入力: arr = [3,1,2,4]
出力: 17
説明:
部分配列は [3]、[1]、[2]、[4]、[3,1]、[1,2]、[ 2,4]、[3,1,2]、[1,2,4]、[3,1,2,4]。
最小値は、3、1、2、4、1、1、2、1、1、1、および 17 です。
問題解決のアイデア
この質問の解決策は、任意の間隔の最小値を取得する機能を持つことです. この質問は、前の質問 (84. ヒストグラムの最大の四角形) と非常によく似ています (作成したばかりで、一番上にあります)。 . 任意の間隔の最小値, 同じ方法を使用して、この質問のすべての要素の最大影響間隔を計算できます. この間隔では、この要素が最小値です.
これにより、最初のターゲット配列が得られます.この配列に格納されている内容は、元の配列の添字に対応し、要素の影響範囲に対応しています.
そして、影響区間は包含関係にあるはずで、数値が小さいほど広い範囲に最小値として存在できるからです。
では、この質問は、どのようにして間隔から影響までの高速な統計手法を生成し、任意の要素の間隔と影響間隔テーブルを生成する必要がありますか?
このルールによれば、任意の要素の影響範囲は、その要素の部分範囲が含まれる限り、すべての部分範囲の最小値がその要素です。
したがって:1巡目で得られた影響区間の配列をたどり、一連の計算式を設計し、要素を含むことができる部分集合がその区間でいくつ生成できるかを求め、その数に要素の値を掛けて、最終結果
特定の間隔に特定の要素が含まれている必要がある場合に生成できるサブセットの最大数を計算するための計算方法は次のとおりです。
- 要素がサブコレクションの左端から右端に移動できる場合、長さは型の数です
- サブコレクションが任意の移動によって要素を含むことができる場合、元のコレクションの長さからサブコレクションの長さを引いたものがタイプの数 + 1 です。
- 残りの場合、要素と元のセットの近い側との間の距離 L は、型の数です。
つまり、近い側の距離を a、遠い側の距離を b として記録します。
- サブセットのサイズが a と b の間にある場合、この時点で結果のタイプを生成できます。
- サブセットのサイズが a より小さい場合、セット サイズの結果は、セット サイズに従って生成できます。
- サブセットのサイズが b よりも大きい場合、元のセットの長さからサブセットの長さを引いたものが、カテゴリの数 + 1 になります。
(上記は問題を解決するための私の考えです。少し複雑だと思うので、次のように別の考えがあります:)
この問題は、任意の間隔の最小値を見つける問題です. 一般に、問題解決の入り口として、最小値を見つけることと任意の間隔の 2 つに分けられます. 間隔の最小値を見つけることは、実際には RMQ 問題であるため、この質問はに変換できます間隔の終わりを順番に固定し、固定後に間隔の左側を移動する RMQ 問題(RMQ(a, b) の場合、b が 0-len 値を実行すると、0-b 値トラバーサルが実行されます)
RMQ に対するこの種の要求は明らかに単調なキューで行われるため、設計コードは次のとおりです。
単調に増加するキューを使用して、すべての要素をキューに入れます。しかし、チームに入るたびに、すべての要素がチームの先頭から取り出され、RMQ (a、b) の結果セットが出力され、チームの先頭からチームに入り、シーンが復元され、増加します。 b チームに入る次のラウンドの準備をします。
上記の 2 つのアイデアがあります。
- モノトニックスタックを使って全要素の影響区間を求める方法
- もう 1 つはモノトニック キューで、RMQ 計算を実行して間隔の最小値を取得します。
(しかし、クラスには別の考え方があります)
それをRMQ問題として理解してください。要素の各ペアは、現在の合計値を提供できます。これは、それ自体の範囲で計算できる合計値に相当します。 (bi-1, bi) plus 、その前の要素 bi が提供できる合計値。
つまり、私が提案した 2 番目のアイデアに戻ります.b の位置が固定されている場合、a の値が 0 から b まで移動する間に生成されるすべての区間の最小値の合計を Sbn と定義し、これにより、数列を取得します。このシーケンスの要素には特徴があり、各ラウンドで単調キューの末尾に位置する Sbn が RMQ (a, b ) の結果と等しくなります。
したがって、末尾の各要素の合計値 Sbn が計算されている限り、次の新しい Sbn を計算したい場合は、それが配置されているモノトニック キューの前の要素が寄与する合計値 Sbn-1 のみが計算されます。 RMQ 加算演算の範囲 (bn -1, bn) の合計値 Sbn-1。
(これはモノトニックキューの性質を利用したものです。各要素がキューの先頭に達した後、その時点からその後の範囲全体までの間隔の最小値であることを意味します)
(これをスキーム 3 として使用すると、私の直感では、スキーム 3 が非常に読みやすいことがわかります)
要約すれば:
- 解決策 1 は、私が提案した要素影響区間の問題です.要素影響区間が得られた後、すべてのサブ区間の数を特定の式で計算し、その数を乗算し、最終的に合計します (この計画では、 2周し、各要素の影響区間を格納する配列を用意する)
- スキーム 2 は私が提案した RMQ 間隔の問題で、パフォーマンスは良くありません. クラスのこのモデルには、より良いパフォーマンスのスキーム 3 があります (このスキームのパフォーマンスについては議論しません)
- スキーム 3 はクラス内のアイデア (抽象化) であり、増加するシーケンスの各要素の優位性を把握する必要があり、本質的には区間影響の計算方法です (このスキームは 1 ラウンドをトラバースし、配列を準備する必要があります)。各要素を最後に取得できる合計値として記録します)
比較してみると、クラスのプランが一番良いと思いますが、私のプランでは十分ではありません
(ε=(´ο`*))) ああ)
(興味のある学生は自分で他の解決策を試すことができます)
サンプルコード
(オプション 3)
class Solution {
private final static Long mod_num = 1000000007L;
public int sumSubarrayMins(int[] arr) {
Stack <Integer> stack = new Stack <Integer> ();
Long ans = 0L;
Long [] sum = new Long [arr.length +1] ;
sum [0] =0L;
for(int i=0;i<arr.length;i++){
while(stack.size()>0&&arr[stack.peek()]>=arr[i]){
stack.pop();
}
int ind = stack.size()>0 ? stack.peek() : -1;
stack.push(i);
sum[stack.size()] = (sum[stack.size() - 1] + arr[i] * (i - ind)) % mod_num;
ans += sum[stack.size()];
ans %= mod_num;
}
return ans.intValue();
}
}
LeetCode 1856。部分配列の最小積の最大値
リンク: https://leetcode-cn.com/problems/maximum-subarray-min-product
配列の最小積は、配列の最小値に配列の合計を掛けたものとして定義されます。
- たとえば、配列 [3,2,5] (最小値は 2) の最小積は 2 * (3+2+5) = 2 * 10 = 20 です。
正の整数配列 nums が与えられた場合、nums の空でない部分配列の最小積の最大値を返してください。答えは非常に大きいかもしれないので、答えの残りを109 + 7にした結果を返してください。
最小積の最大値は、剰余演算の前の結果を考慮していることに注意してください。このトピックは、最小積の最大値を、剰余を取らずに 64 ビットの符号付き整数に格納できることを保証します。
サブ配列は、配列の連続部分として定義されます。
例 1:
入力: nums = [1,2,3,2]
出力: 14
説明: 部分配列 [2,3,2] から最小積の最大値が取得されます (最小値は 2)。
2 * (2+3+2) = 2 * 7 = 14。
問題解決のアイデア
この質問はまだ前の質問に非常に近い. サブ配列の最小積と積の最大値を取得することが期待されます. 因子はサブ配列の最小値です. 特定の要素が含まれると、それは変わりません. 別のダイエットはサブ配列の合計なので、この質問も任意の要素の影響を求める行為です.
任意の要素の影響区間を求めると、すべての要素が正の数であるため、区間の範囲が大きいほど良いため、任意の要素の影響によって生成される積の最大値を取得し、最終的にこの中で最大値を計算します。最大商品価値です。
間隔の合計を容易にするために、最初に、任意の間隔 (a, b) の合計が Sb-Sa に等しくなるように合計シーケンスを生成する必要があります。
ここまでで、この質問に必要な 2 つの因子が生成されました。
(コードは省略、コアコードは任意の要素の影響区間を生成するコード、この部分は前問からコピー可)
エピローグ
時間コスト:
- コンセプト(授業時間+ノート整理)
0.5H+0.5H - 演習 (授業時間 + 思考訓練)
4.75H+0H - コード (興味のあるトピックの手書きコード)
4.5H
合計10.25Hと約2倍の手間が改善
このレッスンは非常に簡単に習得できます. 後の段階で多くのビデオコンテンツをスキップしました. これは, このレッスンの知識が前のレッスンの知識の応用に基づいているためです. このことから, 私が学んだことを確信できます.最後のレッスンはとても上手で、とても良かったです。
しかし、この授業の学習には不十分な点もあり、自分の考えをコードにスムーズに変換するための論理的能力が不足していることに改めて気づきました。やりますが、実際にやったのですが、想像以上に工数の消費が多いです(42.雨水受けなど)。
私の学習スタイルは、
- ビデオに従って概念を学び、
- 方法がわからない場合、またはクラスで解決策を学びたい場合は、クラスから抜け出すのを聞いてください。(一般的に言えば、質問があまりにも単純でない限り、私は自分の考えに最適化の余地があるかどうかを確認するために、授業の終了を聞きます。ある場合は、具体的に説明します)
- 最後に、関心のあるトピックのコードを完成させます
これまでの学習方法と比較して、基本的に省略した内容は次のとおりです。
- コンセプト部分は自分で鮮やかな例を用意
- 演習パートでは、自分で解けない問題は死んでいる
その結果、以前は 5 倍 (20 ~ 25 時間) かかっていた学習時間を 3 倍 (10 ~ 15 時間) 以内に抑えることができるようになりました。ここで生徒たちが助けてくれることを願っています。