76最小カバレッジサブストリング(スライディングウィンドウ)

1.問題の説明:

文字列Sと文字列Tを与えます。文字列Sで見つけてください:Tのすべての文字を含む最小の部分文字列。

例:

入力:S = "ADOBECODEBANC"、T = "ABC"
出力: "BANC"
説明:

Sにそのような部分文字列が存在しない場合、空の文字列 ""が返されます。
そのような部分文字列がSに存在する場合、それが唯一の答えであることを保証します。

ソース:LeetCode
リンク:https ://leetcode-cn.com/problems/minimum-window-substring

2.思考分析:

①このような問題に対して、シミュレーションの方法が明らかに時間外でエラーが発生しやすい場合は、スライディングウィンドウ法を使用してそれを解決できます。スライディングウィンドウに初めて接触したので、公式の解決策を見ました。当時はわかりにくいと感じたのですが、当初はコードがよくわからなかったので、アイデアのデバッグでデバッグし、公式のソリューションと組み合わせてコードを理解しました。アイデア

②スライディングウィンドウは、2つのステップに簡単に分割できます。

1)最初に、問題の要件を満たすスコープを見つけます

2)範囲を特定の範囲内に減らし、得られる結果をさらに絞り込みます

この問題では、最初にTストリングを含むSストリングのサブストリングを判別します。たとえば、SのサブストリングADOBECにはTのすべての文字が含まれ、2番目にはTを含むすべてのコンテンツを検索します。すべての文字の部分文字列には、ADOBEC、BECODEBA、BANCなどの部分文字列があることがわかります。部分文字列の中で最も短いものが解決する必要があるので、考え方は比較的明確です

3)最初にABCを含むすべての部分文字列を見つける必要があります。ここでは、データ構造リストを使用してマップできます。リストの要素はペアタイプ(マップタイプと同様)で、文字Tを文字列T in Sに格納します。出現位置。ペアタイプを使用すると、Tの文字がSのどこに現れるかを簡単に知ることができます。このようなデータ構造を使用してマッピングすると、文字列のどの位置から含まれるかを簡単に計算できます。すべての文字について、以下は、マッピングの関係を明確に示すためにデバッグセクションを使用した図です。

4)Listメソッドを使用して、Tのすべての文字が現在の部分文字列で最短でなければならないことを見つけます。Sに含まれるT文字の位置を使用して計算しているため、さらにレコードを最初にマップする必要があります。文字列Tに含まれる文字の数。すべてのT文字を含む次の部分文字列を探すときに計算する方が適切です。たとえば、最初に見つかった部分文字列はADOBECである必要があります。見つかったとき、Tに表示されている文字に従って検索しました。次にすべてを含むすべてを検索しようとするので、Tに表示される次の文字Bがリストにある位置から検索を開始します(これはリストで簡単に見つけることができます)が必要です。前の文字が削除されます。削除するときは、文字の出現回数を記録するマップ内のマッピングされた文字の数を1だけ減らす必要があります。これにより、次回すべての単語が見つかったときに正しくなります。次回はBECですが、今回は満足されません。ループ内のTにABC文字を含む部分文字列を検索する権利を展開する必要があるため、BECODEBAを見つけることができるので、今回は必要です。2つのwhileループを使用するには、最初のwhileループを使用して右側の境界を拡張し、すべてのABC文字に一致する部分文字列を見つけます。2つ目は、部分文字列の範囲を狭めて短い文字列を見つけます

5)したがって、2番目のwhileループに入るときに、条件が満たされていることを確認する必要があります。このとき、部分文字列の範囲を狭め、最初の部分文字列ADOBECを見つけるなど、条件を満たす部分の文字を削除すると効果があるかどうかを確認します。 、AがTのすべての文字を満たしているかどうかを削除しようとします。条件を満たす場合、ここで削除を続行することはできません(文字Aがないため)。左側の文字を削除すると、ときどき短くなります。 AAOBECの場合、最初のAを削除しても効果はありませんが、結果は部分文字列が短くなるため、削除しても効果がない場合は、削除を続行します。削除後に効果がある場合は、右の境界を拡張する必要があります。

6)3要素配列を使用して、最短の部分文字列の長さ、部分文字列の開始位置と終了位置を更新できるため、最短の部分文字列の位置を最後に取得できます。

7)実は、全体の考え方を理解した上で、それでも比較的単純であり、対応するデータ構造を利用することが鍵なので、公式の問題を理解した上で、自分の考えに基づいて書き直しました。

8)コアはList <Pair <Character、Integer >>であり、すべてのT文字を含む部分文字列を検索できます。Map<Character、Integer>を使用してT内の文字の出現回数をカウントし、文字を削除するときに簡単に計算して見つけることができます。 Tのすべての文字を削除する

9)まず、公式コードを理解する必要があります。それを明確に理解できない場合は、アイデアを参考にして段階的にデバッグし、結果を観察して、より速く理解できるようにします。理解すると、データ構造を学習できます。たとえば、Pairのように、Mapのように、使用時に変更できないようですが、格納された値と値は同じです。他のコードを理解することもできます。それを読んだ後、自分で記述し、自分でエラーを修正する必要があります。あなたがコードを理解することはより役に立ちます

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

私は自分で書きました:

import java.util.*;
public class Solution {
    public String minWindow(String s, String t) {
        /*首先是计算出给出的t字符串中各个字符出现的次数这样在接下来循环删除上一个满足条件的字母的时候才好计算*/
        Map<Character, Integer> charTimes = new HashMap<>();
        for (int i = 0; i < t.length(); ++i){
            /*计算每个字符出现的次数*/
            char c = t.charAt(i);
            //getOrDefault方法可以不用判断之前是否键是否存在避免空指针的异常
            charTimes.put(c, charTimes.getOrDefault(c, 0) + 1);
        }
        /*接下来通过计算字符串中t中每一个字符出现的位置在哪里, 这些位置都是包含着t字符串中的字符的*/
        List<Pair<Character, Integer>> charPos = new ArrayList<>();
        for (int i = 0; i < s.length(); ++i){
            //计算出位置
            char c = s.charAt(i);
            if (charTimes.containsKey(c)){
                //将每个在t字符串中出现的字符标记在list中这样在计算是否包含所有字符串的时候才方便一点
                charPos.add(new Pair<>(c, i));
            }
        }
        /*往字符串进行往右扩展, cur变量用来存储当前在t中出现的匹配的字母个数*/
        int l = 0, r = 0, totalChars = t.length(), cur = 0;
        /*用来记录历史上的最小子串*/
        int ans[] = {-1, 0, 0};
        /*使用一个pair来计算出中间过程中出现的字符个数, pair的功能比map要少一点适合于不用修改的数据*/
        Map<Character, Integer> times = new HashMap<>();
        while (r < charPos.size()){
            /*先是计算出包含着所有字母子串然后再对得到的子串进行缩小*/
            /*获取当前出现字母的位置, pair的位置*/
            char c = charPos.get(r).getKey();
            /*当出现的字母的次数小于当前map对应的字母的个数说明还没有凑够还需要再找*/
            if (times.getOrDefault(c, 0) < charTimes.get(c)) cur++;
            /*下面一句代码需要放到上一个判断的后面*/
            times.put(c, times.getOrDefault(c, 0) + 1);
            /*从子串左端减少字符删除一些不必要的字符, 进入这个循环表示l-r包含着所有的字符
            * 并且循环的条件为删除的字符是没有用的对于构成最短的子串没有什么贡献
            * */
            while (l <= r && cur == totalChars){
                int start = charPos.get(l).getValue();
                int end = charPos.get(r).getValue();
                if (ans[0] == -1 || end - start + 1 < ans[0]){
                    ans[0] = end - start + 1;
                    ans[1] = start;
                    ans[2] = end;
                }
                /*注意下面的是start而不是l*/
                char c1 = s.charAt(start);
                int curCharTimes = times.getOrDefault(c1, 0);
                ++l;
                times.put(c1, curCharTimes - 1);
                /*检查删除当前字符之后是否有影响*/
                if (times.get(c1) < charTimes.get(c1)) {
                    /*表明删除当前的字符有影响因为之前自增了l, 所以需要减去上一个t中含有的字母*/
                    --cur;
                    break;
                }
            }
            /*往右扩展*/
            ++r;
        }
        return ans[0] == -1 ? "" : s.substring(ans[1], ans[2] + 1);
    }
}

公式コード:

class Solution {
  public String minWindow(String s, String t) {
      if (s.length() == 0 || t.length() == 0) {
          return "";
      }
      Map<Character, Integer> dictT = new HashMap<Character, Integer>();
      for (int i = 0; i < t.length(); i++) {
          int count = dictT.getOrDefault(t.charAt(i), 0);
          dictT.put(t.charAt(i), count + 1);
      }
      int required = dictT.size();
      int l = 0, r = 0;
      int formed = 0;
      Map<Character, Integer> windowCounts = new HashMap<Character, Integer>();
      int[] ans = {-1, 0, 0};
      while (r < s.length()) {
          char c = s.charAt(r);
          int count = windowCounts.getOrDefault(c, 0);
          windowCounts.put(c, count + 1);
          if (dictT.containsKey(c) && windowCounts.get(c).intValue() == dictT.get(c).intValue()) {
              formed++;
          }
          while (l <= r && formed == required) {
              c = s.charAt(l);
              // Save the smallest window until now.
              if (ans[0] == -1 || r - l + 1 < ans[0]) {
                  ans[0] = r - l + 1;
                  ans[1] = l;
                  ans[2] = r;
              }
              windowCounts.put(c, windowCounts.get(c) - 1);
              if (dictT.containsKey(c) && windowCounts.get(c).intValue() < dictT.get(c).intValue()) {
                  formed--;
              }
              l++;
          }
          r++;   
      }

      return ans[0] == -1 ? "" : s.substring(ans[1], ans[2] + 1);
  }
}

 

元の記事569件を公開 153のような 訪問数590,000+

おすすめ

転載: blog.csdn.net/qq_39445165/article/details/105301648