大企業の面接官によく聞かれるアルゴリズム図:スタック内の最小値を見つける方法がまだわかりませんか?

大企業の面接官によく聞かれるアルゴリズム図:スタック内の最小値を見つける方法がまだわかりませんか?

今日のインタビューの質問はこれです...

トピック

スタックデータ構造を定義します。この型でスタックの最小要素を取得できる min 関数を実装してください。このスタックでは、min、push、pop を呼び出す時間の計算量は O(1) です。

例:

  1. MinStack を新しい MinStack() に追加します。  
  2. minStack.push(-2);  
  3. minStack.push(0);  
  4. minStack.push(-3);  
  5. minStack.min (); --> -3 を返します。    
  6. minStack.pop();  
  7. minStack.top (); --> 0 を返します。    
  8. minStack.min (); --> -2 を返します。    
  9. LeetCode アドレス: leetcode-cn.com/problems/ba…

考える

まず、この質問自体は理解しやすいのですが、その実装の難しさは次の 2 つの点にあります。

  • ポップ(スタックの一番上の要素を削除する)操作を実行するときに、現在の最小値を削除した場合、次の最小値をどのように見つけるのでしょうか。
  • min、push、popの呼び出しの時間計算量がO(1)であることを確認します。

つまり、pop を実行するときにスタック内の最小の値を削除する場合、スタック内の次の最小の要素をどのように見つけるのでしょうか。また、操作の時間計算量が O(1) であることを確認する必要があります。この時間計算量により、最小値を削除した後に次の最小値を見つけるための走査が制限されるため、これがこの問題の難しさになります。

たとえば、次の最上位要素の値を削除するとします。

最小値は 1 なので、次の図に示すように、削除後に最小値も削除されます。

それでは、3分間考えて、この問題にどう対処するか考えてみましょう〜

問題解決

実際、スタックにプッシュするたびに、現在の要素が最小値より小さいかどうかを判断できます。小さい場合は、元の最小値と最新の最小値を次々にスタックにプッシュできます。このように、pop の呼び出し時に最小値が削除されても、次の要素を取得することで新しい最小値を取得できます。実行プロセスは次のとおりです。

ステップ1

最初の要素をスタックにプッシュします。最初の要素なので、最小値はこの要素の値になります。

ステップ2

以下に示すように、2 番目の要素をスタックにプッシュします。

スタックにプッシュされた要素 3 は 8 より小さいため、スタック内の元の最小値 8 が最初にスタックに格納され、次に 3 がスタックにプッシュされます。

ステップ3

以下に示すように、3 番目の要素をスタックにプッシュします。

スタックにプッシュされた要素 5 は 3 より大きいため、スタック内の最小値は変更されず、要素 5 が直接スタックにプッシュされます。

ステップ4

以下に示すように、スタックをプッシュし続けます。

スタックにプッシュされた要素 1 は 3 未満なので、元の最小値 3 が最初にスタックにプッシュされ、次に 1 がスタックにプッシュされ、スタック内の最小値が 1 に変更されます。

ステップ5

次の図に示すように、ポップ操作を実行します。[画像のアップロード中...(image-f68dcf-1602769401330-6)]

要素 1 がスタックからポップされ、現在の要素がスタックの最小値であると判断されるため、次の図に示すように、最上位の要素 3 が最小値に設定され、要素 3 が削除されます。

ステップ6

以下に示すように、スタックのポップを続けます。

要素 5 は現在の最小値ではないため、スタックから直接ポップされます。

ステップ7

以下に示すように、スタックのポップを続けます。

ポップされた要素 3 は最小値なので、次の図に示すように、引き続き最小値を最上位要素 8 に設定し、最上位要素をポップします。

この方法では、残る要素は 1 つだけです。最後の要素がスタックからポップされると、スタックは空になり、プロセス全体が完了します。

実装コード1

次に、上記のアイデアをコードで実装します。配列によって実装されたスタックを使用して、関連する機能を実装します。コードは次のとおりです。

  1. クラス MinStack {
  2. private int [] data; // スタックデータ
  3. private int maxSize; // 最大長
  4. プライベートint   top ; // スタックの先頭ポインタ(添え字)
  5. プライベートint   min ; // 最小値
  6.  
  7. // コンストラクタ
  8. パブリックMinStack() {
  9. // デフォルト値を設定する
  10. 最大サイズ = 10000;
  11. データ = 新しいint [maxSize];
  12. = -1;
  13. 最小値= Integer.MAX_VALUE ;
  14. }
  15.  
  16. // スタックにプッシュする(要素を追加する)
  17. パブリックボイドプッシュ( int x) {
  18. 最小値>=x)の場合
  19. // より小さい値に遭遇したら、元の最小値を記録する(スタックにプッシュする)
  20. データ[++上部] = min ;
  21. 最小値= x;
  22. }
  23. //現在の値をスタックにプッシュします
  24. データ[++上部] = x;
  25. }
  26.  
  27. // スタックをポップする(一番上の要素を削除する)
  28. パブリックvoidポップ(){
  29. if ( min == data[ top ]) {
  30. min = data[ --top]; // 元の最小値を取得し、スタックからポップします 
  31. }
  32. --top; // スタックをポップする 
  33. }
  34.  
  35. // スタックの一番上の要素を見つける
  36. 公共 整数 トップ() {
  37. データを返す[先頭];
  38. }
  39.  
  40. // 最小値を照会する
  41. 公共 整数 最小(){
  42. 戻る ;
  43. }
  44. }

LeetCode での上記コードの実行結果は次のとおりです。

パフォーマンスは依然として非常に高く、ユーザーの 99.92% を超えており、メモリ消費量も大きくないことがわかります。そのコアコードは push メソッドにあり、最初に元の最小値と最新の最小値をスタックに次々にプッシュします。スタックをポップするときに、ポップされた要素が最小値かどうかを判断します。最小値の場合、現在の最小値はスタックの先頭要素を指し、スタックの先頭要素がポップされ、次の新しい最小値が得られます。

実装コード2

カスタム配列スタックを使用したくない場合は、Java の組み込みスタックを使用してこの関数を実装することもできます。コードは次のとおりです。

  1. クラス MinStack {
  2. プライベート Stack< Integer > stack = new Stack<>();
  3. プライベートint  最小値= Integer.MAX_VALUE ;
  4.  
  5. パブリックMinStack() { }
  6.  
  7. // スタックにプッシュする(要素を追加する)
  8. パブリックボイドプッシュ( int x) {
  9. (x <=最小)の場合{
  10. // より小さい値に遭遇したら、元の最小値を記録する(スタックにプッシュする)
  11. スタックをプッシュします(最小値);
  12. 最小値= x;
  13. }
  14. スタックをプッシュします。
  15. }
  16.  
  17. // スタックをポップする(一番上の要素を削除する)
  18. パブリックvoidポップ(){
  19. (stack.pop() == min )の場合{
  20. min = stack.pop(); // 元の最小値を取得する
  21. }
  22. }
  23.  
  24. // スタックの一番上の要素を見つける
  25. 公共 整数 トップ() {
  26. stack.peek()を返します
  27. }
  28.  
  29. // 最小値を照会する
  30. 公共 整数 最小(){
  31. 戻る ;
  32. }
  33. }

LeetCode での上記コードの実行結果は次のとおりです。

結果から、Java の組み込みスタックを使用した場合のパフォーマンスはカスタム配列のスタックほど良くないことがわかりますが、それでもコードはテストに合格しました。この実装の利点は、コードが比較的単純であり、Java 独自の API を使用して最小値の検索を完了できることです。

このコードの実装方法 (Java API を使用) は、特に指定がない限り、質問の練習や実際の面接で直接使用できます。

要約する

この記事では、カスタム配列スタックと Java API の Stack という 2 つの方法を使用して、スタック内の最小値関数を実装し、スタックの min、push、pop メソッドを呼び出すときの時間計算量が O(1) になるようにします。 2 つの実装方法のコードは若干異なりますが、実装の考え方は同じです。要素をスタックにプッシュするときに、現在の要素が最小要素より小さいかどうかを判断します。最小要素より小さい場合は、最初に元の最小値がスタックにプッシュされ、次に現在の最小要素がスタックにプッシュされます。このように、pop メソッドが呼び出されると、最小値が削除されても、次の要素を取り出して新しい最小値にするだけで済みます。このようにして、min、push、および pop メソッドの呼び出しの時間計算量は O(1) として達成できます。

<<:  新しいドローン産業は急速に発展しているが、まだ3つの大きな障害を取り除く必要がある。

>>:  完全なグラフが利用できない場合にグラフディープラーニングを使用するにはどうすればよいでしょうか?

ブログ    
ブログ    
ブログ    
ブログ    

推薦する

インターネットの価値観を修正するガバナンスアルゴリズム

最近、中国サイバースペース管理局は「インターネット情報サービスアルゴリズム推奨管理規則(草案)」(以...

ワクチン生産を加速するには?答えは医学ではなくテクノロジーにある

世界各国の政府は新型コロナウイルス感染症の流行に対抗するためさまざまな対策を講じているが、世界的な流...

Baidu Create 2018 ディープラーニング フロンティア テクノロジーと産業応用公開コースのハイライト

[51CTO.com からのオリジナル記事] 中国の開発者が集まる毎年恒例の盛大な集まりである Ba...

RPAが企業にもたらすメリットトップ10

この記事では、RPA がビジネスの効率と生産性を向上させる 10 の方法について詳しく説明します。 ...

大規模言語モデルの最大のボトルネック:レート制限

マット・アセイ企画 | ヤン・ジェン制作:51CTO テクノロジースタック(WeChat ID:bl...

ChatGPTはどんどん怠惰になり、代わりにPUA人間を学習しました

GPT-4 が最近少し「怠惰」になっていることにお気づきでしょうか。現在、GPT-4 は常に特定のタ...

MIT テクノロジーレビュー: 6 つの質問が生成 AI の未来を決定する

「生成AIは2023年に世界を席巻します。その未来、そして私たちの未来は、私たちの次の一手によって決...

...

...

致命的な幻覚問題、GPU 代替品の開発、大規模モデルが直面するその他の 10 の課題

ChatGPT、GPT-4などのリリースにより、大規模モデル(LLM)の魅力が明らかになった一方で、...

3人が「テーブルを囲む」脳科学VS人工知能、相互ゲームをどう突破するのか?

IDG Capital の投資家は、神経科学の専門家や最先端技術の起業家とともに、エネルギーと専門...

AI スペクトルをめぐる戦いは 5G にとって何を意味するのでしょうか?

インテリジェントな都市変革の活発なトレンドの中で、AI を使用して交通渋滞を管理することは、誰もが多...

世界の自動運転事故を比較することで、そのデータと真実が明らかになった。

最近起きた自動車事故は、被害者の身元が明らかになったこと、運転支援技術の台頭と普及、中国の有名自動車...

時間との競争! AIは病気の遺伝子解析と診断の加速器である

科学技術分野において、国境を越えた融合による新しいものによってもたらされる破壊的な競争は、あくまでも...

位相データ解析を使用して畳み込みニューラルネットワークモデルの動作プロセスを理解する

1. はじめにニューラル ネットワークは、画像、テキスト、時系列などのさまざまなデータの処理において...