データ構造と区間マージアルゴリズム、貪欲

データ構造と区間マージアルゴリズム、貪欲

[[439314]]

マージ間隔

LeetCode の問題へのリンク: https://leetcode-cn.com/problems/merge-intervals

一連の間隔が指定されている場合、重複するすべての間隔を結合します。

例1:

  • 入力: 間隔 = [[1,3],[2,6],[8,10],[15,18]]
  • 出力: [[1,6],[8,10],[15,18]]
  • 説明: 区間 [1,3] と [2,6] は重複しているので、[1,6] にマージします。

例2:

  • 入力: 間隔 = [[1,4],[4,5]]
  • 出力: [[1,5]]
  • 説明: 区間 [1,4] と [4,5] は重複区間とみなすことができます。
  • 注: 入力タイプは 2019 年 4 月 15 日に変更されました。新しいメソッド シグネチャを取得するには、デフォルトのコード定義をリセットしてください。

ヒント:

間隔[i][0] <= 間隔[i][1]

アイデア

この質問は分類する必要があると誰もが感じているはずですが、左の境界で分類するべきでしょうか、それとも右の境界で分類するべきでしょうか?

大丈夫だよ!

次に、左の境界でソートし、ソート後にローカル最適性が達成されます。つまり、マージするたびに最大の右の境界が取られるため、より多くの間隔をマージでき、全体的な最適性が達成されます。つまり、重複する間隔をすべてマージします。

局所最適性は、全体最適性につながる可能性があります。反例が見つからない場合は、貪欲法を試してください。

すると、何人かの生徒が「最大の右の境界を結合すべきではないですか? これは貪欲とどう関係があるのですか?」と尋ねました。

欲は常識になることもあるよ!ハハ

左境界で小さいものから大きいものの順にソートした後、intervals[i][0] < intervals[i - 1][1]、つまり、intervals[i] 左境界 < intervals[i - 1] 右境界の場合、intervals[i] の左境界は intervals[i - 1] の左境界以上でなければならないため、繰り返しが存在することになります。

つまり、intervals[i]の左境界がintervals[i - 1]の左境界と右境界の範囲内にある場合、繰り返しが存在するはずです。

これは少し抽象的ですが、図を見てください: (図の間隔は左の境界でソートされていることに注意してください)

マージ間隔

重複を判断する方法がわかったら、あとはマージするだけです。マージ間隔をシミュレートするにはどうすればよいでしょうか?

実際には、間隔を新しい間隔として結合した後、左と右の境界を使用して、それを結果配列に追加するだけです。マージがない場合は、元の間隔を結果配列に追加します。

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

  1. クラスソリューション{
  2. 公共
  3. // 区間の左端を小さい順から大きい順に並べ替える
  4. 静的ブール cmp (const ベクトル< int >& a, const ベクトル< int >& b) {
  5. a[0] < b[0]を返します
  6. }
  7. ベクトル<ベクトル< int >> merge(ベクトル<ベクトル< int >>& 間隔) {
  8. ベクトル<ベクトル< int >> 結果;
  9. if ( intervals.size () == 0)結果を返します
  10. ソート(intervals.begin (), intervals.end (), cmp) ;
  11. bool flag = false ; // 最後の間隔が結合されているかどうかをマークします
  12. int長さ = 間隔.サイズ( );
  13.  
  14. ( int i = 1; i < 長さ; i++) {
  15. int start = intervals[i - 1][0]; // 最初はi-1間隔の左境界
  16. 整数  end = intervals[i - 1][1]; // 初期 i-1 間隔の右境界
  17. while (i < length && intervals[i][0] <= end ) { // 間隔をマージする
  18. end = max ( end , intervals[i][1]); // 右の間隔を継続的に更新する
  19. if (i == length - 1) flag = true ; // 最後の区間もマージされる
  20. i++; // 次の区間のマージを続行する
  21. }
  22. // startとendはintervals[i - 1]の左と右の境界を表すので、最適なintervals[i]のintervalがマージされているかどうかをマークする必要があります
  23. 結果.push_back({start, end });
  24. }
  25. // 最後の区間が結合されていない場合は結果に追加します
  26. if (フラグ == false ) {
  27. result.push_back({intervals[length - 1][0], intervals[length - 1][1]});
  28. }
  29. 結果を返します
  30. }
  31. };

もちろん、上記のコードは少し冗長なので、次のように最適化できます。(考え方は同じです)

  1. クラスソリューション{
  2. 公共
  3. ベクトル<ベクトル< int >> merge(ベクトル<ベクトル< int >>& 間隔) {
  4. ベクトル<ベクトル< int >> 結果;
  5. if ( intervals.size () == 0)結果を返します
  6. // ソートパラメータはラムダ式を使用する
  7. ソート( intervals.begin (), intervals.end (), [](const vector< int >& a, const vector< int >& b){ return a[0] < b[0];}) ;
  8.  
  9. 結果.push_back(間隔[0]);
  10. ( int i = 1; i < intervals.size ( ) ); i++) {
  11. if (result.back()[1] >= intervals[i][0]) { // 間隔をマージする
  12. result.back()[1] = max (result.back()[1]、間隔[i][1]);
  13. }それ以外{
  14. 結果.push_back(間隔[i]);
  15. }
  16. }
  17. 結果を返します
  18. }
  19. };
  • 時間計算量: O(nlogn)、クイックソートあり
  • 空間計算量: O(1)、結果配列(戻り値に必要なコンテナが占める空間)は計算しませんでした。

要約する

貪欲アルゴリズムに関して、多くの学生は次のように考えています。「常識に基づいて直接実行できれば、貪欲を使っているとは感じないだろう。一度直感的に理解できなければ、永遠に理解できないかもしれない。」

「Code Random Thoughts」コースを受講した人は、貪欲になるのは難しい、本当に難しいということを経験したはずです。

それで何をすべきでしょうか?

私の貪欲シリーズの冒頭で「貪欲アルゴリズムについて、知っておくべき!」と説明したように、貪欲アルゴリズムにはルーチンやフレームワークがないため、さまざまな従来のソリューションはより多くの露出と練習を必要とし、自然に思い浮かぶようになります。

「Code Thoughts」では、古典的で一般的な貪欲な質問を取り上げます。あなたがしなければならないのは、一生懸命勉強してチェックインすることだけです。

その他の言語

ジャワ

  1. クラスソリューション{
  2. 公共  int [][] merge( int [][] 間隔) {
  3. リスト< int []> res = new LinkedList<>();
  4. 配列.sort(intervals, (o1, o2) -> Integer .compare(o1[0], o2[0]));
  5.  
  6. int開始 = 間隔[0][0];
  7. ( int i = 1; i < 間隔.長さ; i++) {
  8. (間隔[i][0] > 間隔[i - 1][1])の場合{
  9. res.add (新しいint []{start, intervals[i - 1][1]});
  10. 開始 = 間隔[i][0];
  11. }それ以外{
  12. 間隔[i][1] = Math.max (間隔[i][1]、間隔[i - 1][1]);
  13. }
  14. }
  15. res.add (新しいint []{start、intervals[intervals.length - 1][1]});
  16. res.toArray(新しいint [ res.size ()][]);を返します
  17. }
  18. }
  1. // バージョン 2
  2. クラスソリューション{
  3. 公共  int [][] merge( int [][] 間隔) {
  4. LinkedList< int []> res = new LinkedList<>();
  5. 配列.sort(intervals, (o1, o2) -> Integer .compare(o1[0], o2[0]));
  6. res.add (間隔[0]);
  7. ( int i = 1; i < 間隔.長さ; i++) {
  8. (間隔[i][0] <= res.getLast()[1])の場合{
  9. int start = res.getLast()[0];
  10. 整数 終了= Math.max (間隔[i] [ 1]、res.getLast()[1]);
  11. res.removeLast();
  12. res.add (新しいint []{start, end });
  13. }
  14. それ以外{
  15. res.add (間隔[i]);
  16. }
  17. }
  18. res.toArray(新しいint [ res.size ()][]);を返します
  19. }
  20. }

パイソン

  1. クラスソリューション:
  2. def merge(self, intervals: List[List[ int ]]) -> List[List[ int ]]:
  3. len(intervals) == 0の場合:間隔を返す
  4. 間隔.sort(キー=lambda x: x[0])
  5. 結果 = []
  6. 結果.append(間隔[0])
  7. iが範囲(1, len(間隔))内にある場合:
  8. 最後= 結果[-1]
  9. last [1] >= intervals[i][0]の場合:
  10. 結果[-1] = [最終[0],最大値(最終[1], 間隔[i][1])]
  11. それ以外
  12. 結果.append(間隔[i])
  13. 結果を返す

行く

  1. func merge(intervals [][] int ) [][] int {
  2. // 小さい順から大きい順に並べ替える
  3. sort.Slice(intervals,func(i,j int )bool{
  4. 間隔[i][0]<間隔[j][0]を返す
  5. })
  6. // もう一度繰り返す
  7. i:=0;i<len(intervals)-1;i++{の場合
  8. 間隔[i][1]>=間隔[i+1][0]の場合{
  9. intervals[i][1]= max (intervals[i][1],intervals[i+1][1]) // 最大値を割り当てる
  10. 間隔=追加(間隔[:i+1]、間隔[i+2:]...)
  11. 私 -  
  12. }
  13. }
  14. 戻り間隔
  15. }
  16. 関数max (a,b int ) int {
  17. a>b{の場合
  18. 返す
  19. }
  20. bを返す
  21. }

ジャバスクリプト

  1. var merge =関数(間隔) {
  2. 間隔をソートします((a, b) => a[0] - b[0]);
  3. prev = 間隔[0]とする
  4. 結果 = []
  5. ( i =0 とします; i<intervals.length; i++){
  6. cur = intervals[i]とする
  7. 現在の[0] > 前の[1]){
  8. 結果.push(前)
  9. 前 = 現在
  10. }それ以外{
  11. prev[1] = Math.max (cur[1],prev[1])
  12. }
  13. }
  14. 結果.push(前)
  15. 結果を返す
  16. };

バージョン2: 左と右の間隔

  1. /**
  2. * @param {number[][]} 間隔
  3. * @return {数値[][]}
  4. */
  5. var merge =関数(間隔) {
  6. n = intervals.lengthとします。
  7. n < 2の場合、間隔を返します
  8. 間隔をソートします((a, b) => a[0]- b[0]);
  9. res = []とします。
  10. = 間隔[0][0]、
  11. = 間隔[0][1];
  12. ( i = 1; i < n; i++ とします) {
  13. (間隔[i][0] >)の場合{
  14. res.push([,]);
  15. = 間隔[i][0];
  16. = 間隔[i][1];
  17. }それ以外{
  18. = Math.max (intervals[i][1],) ;
  19. }
  20. }
  21. res.push([,]);
  22. resを返します
  23. };

<<:  機械学習における小規模データの重要性

>>:  先進的な自動運転システムの3つの新しい認識機能の分析

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

推薦する

ミュンヘンモーターショーからのシグナル:自動車メーカーがOS開発に躍起になり、中国の力が増す

今年ドイツで開催された国際自動車・スマートモビリティ博覧会(IAA)では、Amazon、Qualco...

ニューラルネットワークにおけるBPアルゴリズムの原理とPython実装のソースコード解析

私は最近、BP アルゴリズムを体系的に研究し、この研究ノートを書きました。私の能力が限られているため...

韓国中央銀行が警告:AIが国内で400万の雇用を奪う可能性

IT Homeは11月16日、韓国銀行が最近、人工知能(AI)が労働市場に与える影響に関する調査報告...

将来、仮想現実、人工知能、そして人体はどのように融合するのでしょうか?

仮想現実や人工知能などのテクノロジーが人体とどのように統合されるかを探ります。将来、仮想現実と現実の...

あなたは人工知能/機械学習についてどれくらい知っていますか?

[[188835]]クイズ番組やマンマシン囲碁で人間に勝ったり、広告で人種差別的な偏見を示したとし...

確かな情報です! AIテクノロジーアーキテクチャソリューションの実現可能性を判断するのに役立つ3つの重要な要素

近年、人工知能は急速に発展しており、コンピュータービジョンや自然言語処理の分野で画期的な変化をもたら...

...

OpenAIがズームイン!史上最強の「モデルストア」が立ち上げられ、すべてのChatGPTアプリケーションを接続する

OpenAI がまたしてもビッグトリックを公開しました!簡単に言えば、サム・アルトマンは市場にあるす...

2019年ロボカップのハイライト!人間が4対1で勝利し、中国チームが多くの賞を獲得した

[[271788]]今月、オーストラリアのシドニーで2019年ロボカップ(ロボットワールドカップ)が...

6軸産業用ロボットの制御方法と特性

[[187760]]産業用ロボットは、産業分野における多関節マニピュレータまたは多自由度機械装置です...

エッジにおける AI について知っておくべきことすべて

近年、人工知能の応用は世界中で大きな進歩を遂げています。職場でのビジネス活動の拡大に伴い、クラウド ...

AIの大規模導入における大きなギャップを埋めます!アリババ、テンセント、百度などが共同でインターネットサービスAIベンチマークを開始

[[276827]]今日、インターネット サービスは根本的な変化を遂げており、徐々にインテリジェント...

...