貪欲アルゴリズム: バイナリツリーを監視したい!

貪欲アルゴリズム: バイナリツリーを監視したい!

[[361051]]

バイナリツリーの問題の監視アドレス: https://leetcode-cn.com/problems/binary-tree-cameras/

バイナリツリーが与えられた場合、ツリーのノードにカメラを設置します。

ノード上の各カメラは、その親、それ自体、およびその直下の子を監視できます。

ツリーのすべてのノードを監視するために必要なカメラの最小数を計算します。

例1:

入力: [0,0,null,0,0]

出力: 1

説明: 図に示すように、すべてのノードを監視するには 1 台のカメラで十分です。

例2:

入力: [0,0,null,0,null,0,null,null,0]

出力: 2

説明: 木のすべてのノードを監視するには、少なくとも 2 台のカメラが必要です。上の画像は、カメラを配置するための有効な位置の 1 つを示しています。

ヒント:

  • 与えられたツリー内のノードの数は [1, 1000] の範囲にあります。
  • 各ノードの値は 0 です。

アイデア

この質問で最初に考えるべきことは、カメラを最小にするにはどのように配置するかということです。

タイトルの例から、実際にインスピレーションを得ることができます。タイトルの例では、カメラがリーフ ノードに配置されていないことがわかりました。

これは非常に重要な手がかりです。カメラは上層、中層、下層をカバーできます。カメラをリーフノードに配置すると、1 層のカバーが無駄になります。

したがって、カメラをリーフノードの親ノードの位置に配置することで、カメラのカバー範囲を最大限に活用できます。

すると、なぜヘッドノードから始めないのか、なぜリーフノードから始めるのかと疑問に思う生徒もいるかもしれません。

ヘッドノードにカメラを配置するかどうかによってカメラが 1 台節約され、リーフノードにカメラを配置するかどうかによってカメラの数が指数関数的に節約されるためです。

したがって、下から上に向かって、局所最適値、つまりリーフ ノードの親ノードにカメラを設置し、使用するカメラの数を最小限に抑え、全体的な最適値、つまり使用するカメラの数が最も少ないものを検討する必要があります。

局所最適解は、全体最適解につながります。反例が見つからない場合は、貪欲なアプローチに従ってください。

この時点での一般的な考え方は、リーフ ノードの親ノードに下から上に向かってカメラを配置し、バイナリ ツリーのヘッド ノードに到達するまで 2 つのノードごとにカメラを配置することです。

この問題には 2 つの難点があります。

  1. 二分木の走査
  2. 2ノードごとにカメラを配置する方法

トラバース順序を決定する

バイナリツリーで下から上に推論するにはどうすればよいでしょうか?

後順トラバーサル、つまり左、右、中央の順序を使用すれば、バックトラック処理中に下から上へ推論することができます。

後順序トラバーサル コードは次のとおりです。

  1. intトラバーサル(TreeNode* cur) {
  2.  
  3. // 空のノード、このノードはカバレッジを持っています
  4. if (終了条件) return ;
  5.  
  6. 整数  left = traversal(cur-> left ); // 左
  7. 整数  right = traversal(cur-> right ); // 右
  8.  
  9. 論理処理 //
  10. 戻る;
  11. }

上記のコードでは、左の子の戻り値と右の子の戻り値、つまり左と右を取得し、中間のノードの状態を推測していることに注意してください。

2ノードごとにカメラを配置する方法

このとき、状態転送式が必要になります。動的状態転送式と混同しないでください。この問題には最適な状態転送プロセスはありません。単純な状態転送だけです。

この状態がどのように転送されるかを見てみましょう。まず、各ノードが複数の状態になる可能性があるかどうかを見てみましょう。

3 つのタイプがあります。

  • このノードはカバレッジがありません
  • このノードにはカメラがあります
  • このノードにはカバレッジがあります

表現する数字は 3 つあります。

  • 0: ノードにカバレッジがありません
  • 1: このノードにはカメラがあります
  • 2: このノードはカバレッジを持っています

おそらく、4 番目のノードのステータスは見つかりません。

生徒の中には、このノードにカメラがないという 4 番目の状態があるのではないかと疑問に思う人もいるかもしれません。実際には、カメラがないということは、カバレッジもカバレッジもないことを意味するため、合計で 3 つの状態が存在します。

ツリーをトラバースする過程で、空のノードに遭遇するので、空のノードはどのような状態にあるかが問題になります。空のノードは、カバーされていないことを意味しますか? カメラがあることを意味しますか? それとも、カバーされていますか?

本質に戻ると、カメラの数を最小限に抑えるためには、リーフノードの親ノードにカメラを設置して、カメラの数を最小限に抑えるようにする必要があります。

すると、空のノードはカバーされていない状態にすることができないため、その場合はリーフ ノードにカメラを配置する必要があります。空のノードはカメラがある状態にすることができないため、その場合はリーフ ノードの親ノードにカメラを配置する必要はありません。代わりに、リーフ ノードの祖父母ノードにカメラを配置できます。

したがって、空のノードの状態のみをカバーすることができ、リーフノードの親ノードにカメラを配置できるようになります。

次は再帰的な関係です。

すると、再帰の終了条件は空のノードに遭遇することになり、その時点で 2 (カバー済み) を返すことになります。その理由は上で説明しました。

コードは次のとおりです。

  1. // 空のノード、このノードはカバレッジを持っています
  2. cur == NULLの場合は2 を返します

再帰関数と終了条件が決まりました。次は単層ロジック処理を見てみましょう。

主な状況は 4 つあります。

  • ケース1: 左ノードと右ノードの両方がカバーされている

左の子はカバーされており、右の子もカバーされているため、中央のノードはカバーされていない状態になるはずです。

図に示すように:


968.バイナリツリーの監視 2

コードは次のとおりです。

  1. // 左ノードと右ノードの両方がカバーされます
  2. == 2 &&== 2 の場合、 0 を返します
  • ケース2: 左ノードと右ノードの少なくとも1つがカバーされていない

次のような場合は、中間ノード(親ノード)にカメラを配置する必要があります。

  • left == 0 && right == 0 左ノードと右ノードはカバーされていません
  • left == 1 && right == 0 左のノードにはカメラがあり、右のノードにはカバレッジがありません
  • left == 0 && right == 1 左のノードが覆われているかどうかに関係なく、右のノードがカメラです
  • left == 0 && right == 2 左のノードはカバーされておらず、右のノードはカバーされている
  • left == 2 && right == 0 左のノードはカバーされており、右のノードはカバーされていません

これは理解するのが難しくありません。結局のところ、カバーされていない子がある場合は、親ノードがカメラを配置する必要があります。

このとき、カメラの数は 1 つ増え、1 を返します。これは、カメラが中間ノードに配置されていることを意味します。

コードは次のとおりです。

  1. if (== 0 ||== 0 ) {
  2. 結果++;
  3. 1 を返します
  4. }
  • ケース3: 左と右のノードの少なくとも1つにカメラがある

次のような状況が発生した場合、左と右の子ノードの1つにカメラがあり、その親ノードは2(カバーされた状態)になります。

  • left == 1 && right == 2 左のノードにはカメラがあり、右のノードにはカバレッジがあります
  • left == 2 && right == 1 左のノードにはカバレッジがあり、右のノードにはカメラがあります
  • left == 1 && right == 1 左ノードと右ノードの両方にカメラがある

コードは次のとおりです。

  1. == 1 ||== 1の場合2 を返します

このコードから、left == 1、right == 0 の場合はどうなるかがわかります。実際、この条件は、図に示すように、ケース 2 で判断されています。


968.バイナリツリーの監視 1

この状況は、ほとんどの学生が簡単に混乱してしまう状況でもあります。

  • ケース4: ヘッドノードがカバーされていない

上記の処理がすべて完了し、再帰が終了した後でも、図に示すように、ヘッドノードがカバーされていない状況がまだ発生する可能性があります。


968.バイナリツリーの監視3

したがって、再帰が終了したら、ルート ノードを決定する必要があります。ルート ノードがカバーされていない場合は、result++ を使用します。コードは次のとおりです。

  1. int最小カメラカバー(TreeNode* ルート) {
  2. 結果 = 0;
  3. if (traversal(root) == 0) { // ルートがカバーされていない
  4. 結果++;
  5. }
  6. 結果を返します
  7. }

上記の 4 つの状況を分析しましたが、コードはほぼ同じです。全体的なコードは次のとおりです。

(以下のコードコメントは非常に詳細です。状況を明確にするために、各ケースをリストします。)

C++ コード

  1. // バージョン 1
  2. クラスソリューション{
  3. プライベート:
  4. int結果;
  5. intトラバーサル(TreeNode* cur) {
  6.  
  7. // 空のノード、このノードはカバレッジを持っています
  8. cur == NULLの場合は2 を返します
  9.  
  10. 整数  left = traversal(cur-> left ); // 左
  11. 整数  right = traversal(cur-> right ); // 右
  12.  
  13. // ケース1
  14. // 左ノードと右ノードの両方がカバーされます
  15. == 2 &&== 2 の場合、 0 を返します
  16.  
  17. // ケース2
  18. // left == 0 && right == 0 左と右のノードはカバーされていません
  19. // left == 1 && right == 0 左のノードにはカメラがあり、右のノードにはカバレッジがありません
  20. // left == 0 && right == 1 左のノードが覆われているかどうかに関係なく、右のノードがカメラです
  21. // left == 0 && right == 2 左のノードはカバーされておらず、右のノードはカバーされています
  22. // left == 2 && right == 0 左のノードはカバーされており、右のノードはカバーされていません
  23. if (== 0 ||== 0 ) {
  24. 結果++;
  25. 1 を返します
  26. }
  27.  
  28. // ケース3
  29. // left == 1 && right == 2 左のノードにはカメラがあり、右のノードにはカバレッジがあります
  30. // left == 2 && right == 1 左のノードにはカバレッジがあり、右のノードにはカメラがあります
  31. // left == 1 && right == 1 左ノードと右ノードの両方にカメラがある
  32. //その他のケースは前のコードでカバーされています
  33. == 1 ||== 1 の場合、 2 を返します
  34.  
  35. // 上記のコードではelseを使用しませんでした。主にさまざまな分岐条件を示し、読者がコードを理解しやすくするためです。
  36. // このreturn -1 ロジックはここでは使用しません。
  37. -1 を返します
  38. }
  39.  
  40. 公共
  41. int最小カメラカバー(TreeNode* ルート) {
  42. 結果 = 0;
  43. // ケース4
  44. if (traversal(root) == 0) { // ルートがカバーされていない
  45. 結果++;
  46. }
  47. 結果を返します
  48. }
  49. };

上記のコードに基づいて、コードは次のように簡略化されます。

  1. // バージョン 2
  2. クラスソリューション{
  3. プライベート:
  4. int結果;
  5. intトラバーサル(TreeNode* cur) {
  6. cur == NULLの場合は2 を返します
  7. 整数  left = traversal(cur-> left ); // 左
  8. 整数  right = traversal(cur-> right ); // 右
  9. == 2 &&== 2 の場合、 0 を返します
  10. そうでない場合 (== 0 ||== 0 ) {
  11. 結果++;
  12. 1 を返します
  13. }それ以外  2を返します
  14. }
  15. 公共
  16. int最小カメラカバー(TreeNode* ルート) {
  17. 結果 = 0;
  18. if (traversal(root) == 0) { // ルートがカバーされていない
  19. 結果++;
  20. }
  21. 結果を返します
  22. }
  23. };

こんなに短くなるなんて、驚かれるかもしれません。実際、これはバージョン 1 に基づいており、else を使用していくつかの状況を直接カバーしています。

この問題の解決方法については、インターネット上で神レベルのコードが多数見つかりますが、どれも明確に説明されていません。コードを直接見ると、ますます混乱するでしょう。そのため、バージョン 1 のコードを段階的に実行することをお勧めします。バージョン 2 は役に立ちません。

要約する

この問題の難しさは、まず貪欲な考え方を考え、次に推論をたどって述べることです。

実際、バイナリ ツリー上の状態推論の難しさはより高いレベルにあり、バイナリ ツリーの非常に熟練した操作が必要です。

この記事はWeChatの公開アカウント「Code Thoughts」から転載したもので、以下のQRコードからフォローできます。この記事を転載する場合は、Code Thoughts の公開アカウントにご連絡ください。

<<:  エッジAIとクラウドAIのバランスを見つける

>>:  携帯電話を使ってドライバーを監視:ドライバーレコーダーもAI技術を活用し始めている

ブログ    

推薦する

サイバーセキュリティを変える、最もホットなハッカーツール:武器化された人工知能FraudGPT

FraudGPT の「成功」は、生成 AI の武器化とハッキング技術の民主化という危険な時代の到来...

...

アリババが自社開発のAIクラスターの詳細を発表:64基のGPU、数百万のカテゴリーのトレーニングを4倍高速化

この記事はAI新メディアQuantum Bit(公開アカウントID:QbitAI)より許可を得て転載...

MIT スタンフォード トランスフォーマーの最新研究: 過剰トレーニングにより、中程度のモデルが構造一般化能力を「発現」できるようになる

人間にとって、文章は階層的です。文の階層構造は表現と理解の両方にとって非常に重要です。しかし、自然言...

韓国チームはサンプルの引き渡しを拒否し、2本目のLK-99サスペンションビデオを公開しました! HUSTの新論文が初めて反磁性を証明

昨夜、「LK-99は韓国当局により偽物と摘発され、常温超伝導体ではない」というニュースがインターネッ...

科学記事:強化学習後、ロボット学習のボトルネックをどう突破するのか?

[[340407]]この記事はLeiphone.comから転載したものです。転載する場合は、Lei...

李菲菲の「具現化された知能」はどこまで進歩したのか?

2009年、当時プリンストン大学に勤務していたコンピューター科学者のフェイフェイ・リー氏が、人工知...

スタンフォードのAIエージェント研究が熱い! 「好奇心リプレイ」アルゴリズムにより、AIは自分自身を振り返り、積極的に新しい世界を探索することができる。

一夜にして、AI エージェントが突然インターネット全体を支配しました。業界のリーダーたちは、その焦点...

...

百度の新しいAI翻訳機は80以上の言語をリアルタイムで翻訳できる

海外旅行の際、最大の問題は言語かもしれません。相手の言っていることを理解できれば、他のコミュニケーシ...

Java プログラミング スキル - データ構造とアルゴリズム「単方向リンク リスト」

[[386512]]基本的な紹介リンクリストは順序付きリストですが、メモリ内に次のように保存されま...

ルカン氏は、今後10年間の研究計画に関する62ページの論文を発表した。AI自律知能

この記事はAI新メディアQuantum Bit(公開アカウントID:QbitAI)より許可を得て転載...

OpenAI、開発者向けGPTチャットボットAPIのメジャーアップデートを発表、価格を値下げ

OpenAI は本日、大規模言語モデル API (GPT-4 および gpt-3.5-turbo を...

データだけ? 2018 年の AI 予測トップ 5

[[213487]] 2017年、人工知能(AI)は職場でも家庭でも、ほとんどの人々の日常生活の一...