[C++ コード] 雨水を収集する、最も近い大きな要素、ヒストグラム内の最大の行列、単調スタック - コードの考察

トピック:毎日の気温

  • 毎日の気温を表す整数の配列temperaturesが与えられると、配列answerが返されます。answer[i] はその日を指します。 i、次に高温になるのは数日後です。これでも温度が上がらない場合は、0 を使用してください。

  • 結果の配列を更新するためのブルート フォース ソリューション、2 レベルの for ループ

    • class Solution {
      public:
          vector<int> dailyTemperatures(vector<int>& temperatures) {
              vector<int> res(temperatures.size());
              for(int i=0;i<temperatures.size();i++){
                  int dis=0;
                  bool flag=true;
                  for(int j=i+1;j<temperatures.size();j++){
                      if(temperatures[i]>=temperatures[j]){
                          dis++;
                      }else{
                          dis++;
                          flag = false;
                          break;
                      }
                  }
                  if(flag){
                      dis=0;
                  }
                  res[i] = dis;
              }
              return res;
          }
      };//超时
      
  • 温度リストの各要素、temperatures[i] について、i < j、かつ、temperatures[i] < pressures[j] となる最小のインデックス j を見つける必要があります。温度範囲は [30, 100] 以内であるため、配列を維持して、各温度の最初の発生のインデックスを記録することができます。配列 next の要素は無限大に初期化され、next の値は温度リストの反復中に更新されます。

  • 温度リストを逆の順序で調べます。各要素の温度 [i] について、次に配列内の温度 [i] + 1 から 100 までの各温度の最初の出現のインデックスを見つけ、それらの中で最も小さいインデックスを WarmIndex としてマークします。その後、warmIndex が次の温度になります。今日よりも。 WarmIndex が無限でない場合、warmIndex - i は次の気温が現在の日より高くなるまでの待機日数であり、最終的に next[temperatures[i]] = i とします。

  • class Solution {
    public:
        vector<int> dailyTemperatures(vector<int>& temperatures) {
            int n=temperatures.size();
            vector<int> ans(n),next(101,INT_MAX);
            for(int i=n-1;i>=0;i--){
                int index=INT_MAX;
                for(int t = temperatures[i]+1;t<=100;++t){
                    index = min(index,next[t]);
                }
                if(index!=INT_MAX){
                    ans[i]=index-i;
                }
                next[temperatures[i]]=i;
            }
            return ans;
        }
    };
    
  • 時間計算量: O(nm))、n は温度リストの長さ、m は次の配列の長さです。この質問では温度は 100 を超えないため、m の値は 100 です。温度リストを逆方向に走査します。温度リストの各値について、次に配列を走査します。空間計算量: O(m)、ここで m は配列の長さです。戻り値に加えて、各温度の最初の出現の添字位置を記録するために、長さ m の配列 next を維持する必要があります。

  • は通常 1 次元の配列です。それ自体より大きいまたは小さい要素の右側または左側の最初の要素の位置を見つけるには、現時点では単調スタックの使用を考える必要があります< /span> 配列を走査するとき、配列がどのような要素であるかわからないからです。そのため、要素を走査しても、より小さい要素が以前に走査されたかどうかを見つけることができないため、コンテナ (ここでは単調スタック) を使用して、走査した要素を記録する必要があります。 より簡単に言うと、スタックは、走査した要素を記録するために使用されます。。これは、走査プロセス中に、スタックが右側の最初の要素を記録する必要があるためです。は現在の要素よりも大きいため、配列全体を一度走査するだけで済むという利点があります。 単調スタックの本質は、空間を時間と交換することです。時間計算量は O(n) です。

    • 単調スタックにはどのような要素が格納されますか?単調スタックには要素の添字 i を格納するだけでよく、対応する要素を使用する必要がある場合は、T[i] から直接取得できます。
    • 単調スタック内の要素は増加していますか?それとも減ってきているのでしょうか?ここでは、昇順 (ここでもスタックの一番上からスタックの一番下までの順序) を使用する必要があります。これは、要素 i がスタックに追加されるとき、インクリメントする場合にのみ、スタックの最上位の要素がわかるからです。 stack は配列の右側の最初の要素です。スタックの最上位の要素より大きい要素は i です。つまり、要素の右側に最初の大きな要素が見つかった場合、単調スタックは増加し、要素の右側に最初の小さな要素が見つかった場合、単調スタックは減少しています。
  • class Solution {
          
          
    public:
        vector<int> dailyTemperatures(vector<int>& temperatures) {
          
          
            stack<int> st;
            vector<int> res(temperatures.size(),0);
            st.push(0);//0表示下标
            for(int i=1;i<temperatures.size();i++){
          
          
                if(temperatures[i]<temperatures[st.top()]){
          
          
                    st.push(i);
                }else if(temperatures[i]==temperatures[st.top()]){
          
          
                    st.push(i);
                }else{
          
          
                    while(!st.empty() && temperatures[i] > temperatures[st.top()]){
          
          
                        res[st.top()] = i-st.top();
                        st.pop();
                    }
                    st.push(i);
                }
            }
            return res;
        }
    };
    
  • 時間計算量: O(n); 空間計算量: O(n)

タイトル:次に大きい要素 I

  • nums1 の数値x の次に大きい要素x です。 nums2 の対応する位置 の右側 の最初 が要素です より大きい。反復要素のない 2 つの 配列 が与えられ、 の添字が始まります。 0 がカウントを開始します。 のサブセットです。それぞれの について、 を満たす添え字 を見つけ、 < で決定します。 /span>次に大きい要素 を答えとして返します。 が上記のような長さの配列 になります。 。次に大きい要素がない場合、このクエリに対する答えは 次に大きい要素xnums1nums2nums1nums20 <= i < nums1.lengthnums1[i] == nums2[j]jnums2nums2[j]-1nums1.lengthansans[i]

  • 残酷なソリューション、2 層の for ループ

    • class Solution {
      public:
          vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
              vector<int> res(nums1.size(),-1);
              for(int i=0;i<nums1.size();i++){
                  bool flag = false;
                  for(int j=0;j<nums2.size();j++){
                      if(nums1[i]==nums2[j]){
                          flag = true;
                      }
                      if(flag && nums1[i]<nums2[j]){
                          res[i] = nums2[j];
                          break;
                      }
                  }
              }
              return res;
          }
      };
      
  • 質問例から、最終的に、nums1 の各要素は、現在の要素よりも大きい nums2 の次の要素である必要があることがわかります。そのため、nums1 と同じサイズの配列結果を定義する必要があります。結果を保存します。質問文では、対応する位置が無い場合は-1を出力するとありますので、結果配列の特定の位置に値が代入されていない場合は-1になるはずなので、-1に初期化します。

  • nums2 を走査するプロセスでは、最終的に nums1 要素の添字に基づいて結果の配列を更新する必要があるため、nums2[i] が nums1 に出現したかどうかを判断する必要があります。要素が繰り返されなければ、マッピングにマップを使用できます。値に基づいて添え字をすばやく見つけ、nums2[i] が nums1 に含まれるかどうかも判断できます。

  • スタックの一番上からスタックの一番下までの順序は、小さいものから大きいものにする必要があります。つまり、スタック内の要素を昇順に保つ必要があります。インクリメントを続ける限り、右側で自分より大きい最初の要素を見つけることができます。次に、明確に分析する必要がある次の 3 つの状況を分析します。

    • ケース 1: 現在トラバースされている要素 T[i] がスタックの最上位要素 T[st.top()] より小さい; このとき、増分スタック (スタックの最上位から最下位までの順序) stack) が満たされるため、スタックに直接プッシュされます。
    • ケース 2: 現在トラバースされている要素 T[i] がスタックの最上位要素 T[st.top()] と等しい。
    • 状況 3: 現在トラバースされている要素 T[i] がスタックの最上位要素 T[st.top()] より大きい: この時点でスタックにプッシュされると、増分スタックは満たされません。それ自体より大きい右側の最初の要素を見つけます。スタックの最上位の要素が nums1 に出現したかどうかを確認し (スタック内の要素は nums2 の要素であることに注意してください)、出現した場合は、結果の記録を開始します。
  • class Solution {
    public:
        vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
            stack<int> st;
            vector<int> res(nums1.size(),-1);
            if(nums1.size()==0){
                return res;
            }
            unordered_map<int,int> umap;// key:下标元素,value:下标
            for(int i=0;i<nums1.size();i++){
                umap[nums1[i]]=i;
            }
            st.push(0);
            for(int i=1;i<nums2.size();i++){
                if(nums2[i]<nums2[st.top()]){
                    st.push(i);
                }else if(nums2[i] == nums2[st.top()]){
                    st.push(i);
                }else{
                    while(!st.empty() && nums2[i] > nums2[st.top()]){
                        if(umap.count(nums2[st.top()])>0){
                            int index = umap[nums2[st.top()]];
                            res[index] = nums2[i];
                        }
                        st.pop();
                    }
                    st.push(i);
                }
            }
            return res;
        }
    };
    

タイトル:次に大きな要素 II

  • 円形配列を指定した場合nums (nums[nums.length - 1] の次の要素は nums[0] です)、 < /span>nums の各要素の次に大きい要素。数値x次に大きい要素は配列走査順序であり、この数値の後の最初の要素はそれより大きいです大きい数値。ループして次に大きい数値を検索する必要があることを意味します。存在しない場合は、 -1 を出力します。

  • 2 つの配列を結合し、単調スタックを使用して次の最大値を見つけるだけです。それは効果があります!

  • class Solution {
          
          
    public:
        vector<int> nextGreaterElements(vector<int>& nums) {
          
          
            vector<int> nums1(nums.begin(),nums.end());
            nums.insert(nums.end(),nums1.begin(),nums1.end());
            vector<int> res(nums.size(),-1);
            if(nums.size()==0){
          
          
                return res;
            }
            stack<int> st;
            st.push(0);
            for(int i=1;i<nums.size();i++){
          
          
                if(nums[i]<nums[st.top()]){
          
          
                    st.push(i);
                }else if(nums[i]==nums[st.top()]){
          
          
                    st.push(i);
                }else{
          
          
                    while(!st.empty() && nums[i] > nums[st.top()]){
          
          
                        res[st.top()] = nums[i];
                        st.pop();
                    }
                    st.push(i);
                }
            }
            res.resize(nums.size()/2);
            return res;
        }
    };
    
  • この書き方は確かに直感的ですが、nums 配列の変更や、最終的に結果の配列のサイズを元に戻すなど、多くの無駄な操作を実行します。サイズ変更は時間がかかり、O(1) 操作ですが、nums 配列の拡張は追加の O(n) 操作と同等です。

    • class Solution {
      public:
          vector<int> nextGreaterElements(vector<int>& nums) {
              int len = nums.size();
              vector<int> res(len,-1);
              if(len == 0){
                  return res;
              }
              stack<int> st;
              st.push(0);
              for(int i=1;i<len*2;i++){
                  if(nums[i % len] < nums[st.top()]){
                      st.push(i%len);
                  }else if(nums[i%len] == nums[st.top()]){
                      st.push(i%len);
                  }else{
                      while(!st.empty() && nums[i%len]>nums[st.top()]){
                          res[st.top()]=nums[i%len];
                          st.pop();
                      }
                      st.push(i%len);
                  }
              }
              return res;
          }
      };
      

题目:接雨水

  • 幅を持つ各列の高さマップを表す非負の整数がn場合、このように配置された列の高さを計算します。雨が降ります どのくらいの雨が降りますか? 与えられた1

  • この問題に対する暴力的な解決策でも、ダブル ポインタを使用します。まず、行に従って計算するのか、列に従って計算するのかを明確にする必要があります。

    • ここに画像の説明を挿入します
  • まず、列ごとに計算する場合、幅は 1 である必要があります。その後、各列の雨水の高さを見つけることができます。 **各雨水柱の高さは、柱の左側の最も高い柱の高さと、右側の最も高い柱の中で最も短い柱の高さに依存することがわかります。

  • class Solution {
          
          
    public:
        int trap(vector<int>& height) {
          
          
            int sum=0;
            for(int i=0;i<height.size();i++){
          
          
                if(i==0 || i==height.size()-1){
          
          
                    continue;
                }
                int rH=height[i];
                int lH= height[i];
                for(int r=i+1;r<height.size();r++){
          
          
                    if(height[r]>rH){
          
          
                        rH = height[r];
                    }
                }
                for(int l=i-1;l>=0;l--){
          
          
                    if(height[l]>lH){
          
          
                        lH = height[l];
                    }
                }
                int h=min(rH,lH) - height[i];
                if(h>0){
          
          
                    sum+=h*1;
                }
            }
            return sum;
        }
    };//超时
    
  • 列を走査するたびに、両側で最も高い列を探す必要があるため、時間計算量は O(n^2)、空間計算量は O(1) になります。ダブルポインタ方式の最適化が可能

    • class Solution {
      public:
          int trap(vector<int>& height) {
              if(height.size()<=2){
                  return 0;
              }
              vector<int> maxleft(height.size(),0);
              vector<int> maxright(height.size(),0);
              int size=maxright.size();
              maxleft[0] = height[0];
              for(int i=1;i<size;i++){// 记录每个柱子左边柱子最大高度
                  maxleft[i]=max(height[i],maxleft[i-1]);
              }
              maxright[size-1]=height[size-1];
              for(int i=size-2;i>=0;i--){// 记录每个柱子右边柱子最大高度
                  maxright[i]=max(maxright[i+1],height[i]);
              }
              int sum=0;
              for(int i=0;i<size;i++){
                  int count=min(maxleft[i],maxright[i])-height[i];
                  if(count>0){
                      sum +=count;
                  }
              }
              return sum;
          }
      };
      
  • は通常、1 次元の配列です。 それ自体より大きいまたは小さい要素の右側または左側の最初の要素の位置を見つけるには、現時点では次のようにします。単調スタックはの使用を考えなければなりません。雨水を集める問題に関しては、雨水の面積を計算するには、右側の最大要素と左側の最大要素の要素を見つける必要があります。

  • まず、単調スタックは、単調スタック内の要素の順序を使用して、行方向に従って雨水を計算します。スタックの先頭 (要素はスタックの先頭からポップされます) からスタックの底部までの順序は、小さいものから大きいものになる必要があります。 。追加された列の高さがスタック ヘッド要素よりも大きいことが判明すると、溝が表示されます。スタック ヘッド要素は溝の底にある列であり、スタック ヘッドの 2 番目の要素はその上の列です。溝の左側、追加された要素はスロットの右側の凹柱です。

    • ここに画像の説明を挿入します
  • 同じ要素に遭遇した場合、スタック内の添え字を更新するということは、要素 (古い添え字) をスタックからポップし、新しい要素 (新しい添え字) をスタックに追加することを意味します。

  • スタックに格納する値: 単調スタックを使用すると、雨水の面積も長さ * 幅で計算されます。長さは列の高さによって計算され、幅は列間の添え字によって計算されます。したがって、要素の高さと添え字を保存するには、pair

    • class Solution {
      public:
          int trap(vector<int>& height) {
              if(height.size()<=2){
                  return 0;
              }
              stack<int> st;
              st.push(0);
              int sum=0;
              for(int i=1;i<height.size();i++){
                  if(height[i]<height[st.top()]){
                      st.push(i);
                  }else if(height[i] == height[st.top()]){
                      st.push(i);
                  }else{
                      while(!st.empty() && height[i] > height[st.top()]){
                          int mid=st.top();
                          st.pop();
                          if(!st.empty()){
                              int h=min(height[st.top()],height[i])-height[mid];
                              int w=i-st.top()-1;
                              sum += h*w;
                          }
                      }
                      st.push(i);
                  }
              }
              return sum;
          }
      };
      
  • この時点で、それが実際にはスタックの最上位であること、スタックの最上位の次の要素であること、スタックにプッシュされる要素であることがわかるはずです。水! **雨水の高さは min (溝の左側の高さ、溝の右側の高さ) - 溝の底の高さ、コードは次のとおりです: < a i=1>雨水の幅は、溝の右側の添字 - 溝の左側の添字 - 1 (中央の幅しか見つからないため)、コードは次のとおりです。 < a i=2>現在の溝の雨水の体積は次のとおりです: int h = min(height[st.top()], height[i]) - height[mid];int w = i - st.top() - 1 ;h * w

トピック:ヒストグラム内の最大の長方形

  • 指定された n 個の非負の整数。ヒストグラムの各列の高さを表すために使用されます。各列は互いに隣接しており、幅は 1 です。このヒストグラムで描画できる長方形の最大面積を求めます。

  • 暴力的な解決策、タイムアウト

    • class Solution {
              
              
      public:
          int largestRectangleArea(vector<int>& heights) {
              
              
              int sum=0;
              for(int i=0;i<heights.size();i++){
              
              
                  int left=i;
                  int right=i;
                  for(;left>=0;left--){
              
              
                      if(heights[left]<heights[i]){
              
              
                          break;
                      }
                  }
                  for(;right<heights.size();right++){
              
              
                      if(heights[right]<heights[i]){
              
              
                          break;
                      }
                  }
                  int w=right-left-1;
                  int h=heights[i];
                  sum=max(sum,h*w);
              }
              return sum;
          }
      };
      
  • この質問では、列よりも小さい最初の左列の高さではなく、その列よりも小さい最初の左列の添え字を記録する必要があります。

    • class Solution {
      public:
          int largestRectangleArea(vector<int>& heights) {
              int len = heights.size();
              vector<int> minleft(len);
              vector<int> minright(len);
              // 记录每个柱子 左边第一个小于该柱子的下标
              minleft[0]=-1;
              for(int i=1;i<len;i++){
                  int t=i-1;
                  while(t>=0 && heights[t]>=heights[i]){
                      t = minleft[t];
                  }
                  minleft[i] = t;
              }
              minright[len-1]=len;
              for(int i=len-2;i>=0;i--){
                  int t=i+1;
                  while(t<len && heights[t]>=heights[i]){
                      t = minright[t];
                  }
                  minright[i]=t;
              }
              int res=0;
              for(int i=0;i<len;i++){
                  int sum = heights[i]*(minright[i]-minleft[i]-1);
                  res = max(res,sum);
              }
              return res;
          }
      };
      
  • これには、単調スタックの非常に重要な特性が関係します。これは、単調スタック内の順序 (小から大、または大から小) です。この質問は、各列の左側と右側でその列より小さい最初の列を見つけることであるため、スタックの一番上 (要素はスタックの一番上からポップされる) から一番下までの順序になります。スタックは大きいものから小さいものまで存在する必要があります。

    • ここに画像の説明を挿入します
  • スタック内の大きいものから小さいものへの順序でのみ、スタックの一番上の要素は、スタックの一番上の要素よりも小さい左右の最初のピラーを見つけることができます。この時点で、それが実際にはスタックの最上部、スタックの最上部の次の要素、および最大領域の高さと幅を形成するスタックにプッシュされる 3 つの要素であることがわかるはずです。必要です。重要なことは、次の 3 つの状況を明確に分析することです。

    • ケース 1: 現在トラバースされている要素の高さ [i] がスタックの最上位の要素の高さ [st.top()] より大きい
    • ケース 2: 現在トラバースされている要素の高さ [i] がスタックの最上位の要素の高さ [st.top()] に等しい
    • ケース 3: 現在トラバースされている要素の高さ [i] がスタックの最上位の要素の高さ [st.top()] より小さい
  • class Solution {
    public:
        int largestRectangleArea(vector<int>& heights) {
            int res=0;
            stack<int> st;
            heights.insert(heights.begin(),0);// 数组头部加入元素0
            heights.push_back(0);
            st.push(0);
            for(int i=1;i<heights.size();i++){
                if(heights[i]>heights[st.top()]){
                    st.push(i);
                }else if(heights[i] == heights[st.top()]){
                    st.push(i);
                }else{
                    while(!st.empty() && heights[i] < heights[st.top()]){
                        int mid=st.top();
                        st.pop();
                        if(!st.empty()){
                            int left=st.top();
                            int right=i;
                            int w=right-left-1;
                            int h=heights[mid];
                            res=max(w*h,res);
                        }
                    }
                    st.push(i);
                }
            }
            return res;
        }
    };
    

おすすめ

転載: blog.csdn.net/weixin_43424450/article/details/134234058