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

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

[[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つの新しい認識機能の分析

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

推薦する

労働者はなぜ人工知能を恐れるべきなのでしょうか?

人工知能の概念は何年も前から存在しています。SF映画に出てくるような高度なロボットはまだ登場していま...

マイクロソフトアジアリサーチは、知識蒸留を使用して小さなViTを改善するTinyMIMを提案

1. 研究の動機マスクモデリング (MIM、MAE) は、非常に効果的な自己教師ありトレーニング方法...

...

5G+AIは通信とコンピューティングを統合する

人工知能(AI)の急速な発展は、さまざまな業界に革命的な変化をもたらし、イノベーションの新たな時代を...

美団は食品配達に「ドローン」を使う予定?テクノロジーは飛躍的な進歩を遂げました!

以前のPC時代では、人々は携帯電話やウェブページを通じて近くのレストランに注文をしていたが、これには...

機械学習における 5 つのよくある問題点とその解決方法

[[394332]]機械学習のさまざまな使用例について聞いたことがあるかもしれません。たとえば、カン...

機械学習のコンテナ化: TensorFlow、Kubernetes、Kubeflow

[[253678]] [51CTO.com クイック翻訳] 機械学習 (ML) は、パターンを識別...

2022年の人工知能ロボットの5つのトレンド

ロボット工学は近年驚異的な進歩を遂げました。ロボティックプロセスオートメーションなどの分野は、ますま...

...

...

自動運転車の長所と短所

長年にわたる技術の進歩により、交通はより便利になりました。 IoT アプリケーションなどの自動車技術...

第4世代ロボットが発売。Lingdong TechnologyのAMR分野における粘り強さと革新

AGV と比較すると、V-AMR ロボットの利点は、特にビジネス プロセス、倉庫の変革、展開サイクル...

...

デジタルマーケティング: AI はどのようにして人間の行動パターンを「見抜く」のでしょうか?

この記事は公開アカウント「Reading Core Technique」(ID: AI_Discov...

...