Technologieaustausch

【Leetcode】Schiebefensterthema

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Fügen Sie hier eine Bildbeschreibung ein

1. Subarray mit minimaler Länge

Fügen Sie hier eine Bildbeschreibung ein
Leetcode 209. Subarray mit minimaler Länge

Wenn man sich dieses Thema anschaut, fällt einem als Erstes die gewalttätige Aufzählung ein. Versuchen wir also, Folgendes aufzuzählen:
Fügen Sie hier eine Bildbeschreibung ein

Offensichtlich kommt es bei der Brute-Force-Aufzählungsmethode zu einer Zeitüberschreitung.

Fügen Sie hier eine Bildbeschreibung ein

Gibt es also eine Möglichkeit, es so zu optimieren, dass es nicht zu einer Zeitüberschreitung kommt? Lassen Sie es uns analysieren:

Bei einer heftigen Aufzählung kehrt unsere Rechte jedes Mal zur nächsten Position der Linken zurück und zählt dann zusammen mit der Linken erneut auf.

Fügen Sie hier eine Bildbeschreibung ein
Die spezifischen Schritte zur Optimierung der Aufzählung sind wie folgt:

Fügen Sie hier eine Bildbeschreibung ein

Im obigen Aufzählungsprozess ist unsere blaue Box wie ein Schiebefenster mit nicht festgelegter Größe. Wir nennen diese MethodeSchiebefenster

Bei Schiebefenstern sind es nichts weiter als die folgenden Schritte:
Fügen Sie hier eine Bildbeschreibung ein
Der Ausgangspunkt jedes Fragefensters und die Position der aktualisierten Ergebnisse sind nicht festgelegt und die spezifische Situation wird von Fall zu Fall analysiert.

Gemäß der Schiebefenstermethode kommt es bei unserem Code nicht zu einer Zeitüberschreitung.
Fügen Sie hier eine Bildbeschreibung ein
Analysieren Sie kurz die folgende Zeitkomplexität: Aus dem Code geht hervor, dass sie O(n) ist2), aber aufgrund unserer Schiebefenster-Idee bewegen sich die beiden Zeiger in die gleiche Richtung und gehen nicht zurück, wenn der rechte Zeiger endet, also endet die SchleifeDie Zeitkomplexität beträgt O(n).

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int ret = INT_MAX;
        int n = nums.size();
        int sum = 0;
        for(int left = 0, right = 0; right < n; right++)//right后移
        {
            //进窗口
            sum += nums[right];
            //判断窗口
            while(sum >= target)
            {
                //符合条件,更新结果
                ret = min(ret,right - left + 1);
                sum -= nums[left];//出窗口
                left++;
            }  
        }
        if(ret == INT_MAX)
            return 0;
        else
            return ret;
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

2. Die längste Teilzeichenfolge ohne wiederholte Zeichen

Fügen Sie hier eine Bildbeschreibung ein
Leetcode 3. Die längste Zeichenfolge ohne wiederholte Zeichen

Die erste Methode, die mir für diese Frage in den Sinn kommt, ist zweifellos die Aufzählung und das anschließende Zählen, wie oft jedes Zeichen vorkommt. Wenn ein Zeichen zweimal vorkommt, ist die Zeichenfolge, die dem aktuellen Zeichen entspricht, die längste Teilzeichenfolge.
Fügen Sie hier eine Bildbeschreibung ein
Wir haben festgestellt, dass auch die Brute-Force-Aufzählungsmethode erfolgreich sein kann
Fügen Sie hier eine Bildbeschreibung ein

Gibt es eine bessere Lösung?Lassen Sie es uns analysieren
Fügen Sie hier eine Bildbeschreibung ein
Ist die obige Lösung nicht nur ein Schiebefenster?
Fügen Sie hier eine Bildbeschreibung ein

Ist die Zeitkomplexität O(n) zu diesem Zeitpunkt besser als die der gewaltsamen Aufzählung O(n)?2) ist sogar noch besser

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int n = s.size();
        int hash[128] = { 0 };
        int ret = 0;
        for(int left = 0, right = 0; right < n; right++)
        {
            hash[s[right]]++;//入窗口,先记录当前
            while(hash[s[right]] > 1)//若s[right]重复
            {
                hash[s[left]]--;//出窗口
                left++;
            }
            ret = max(ret,right - left + 1);//更新结果
        }
        return ret;
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

3. Maximale Anzahl aufeinanderfolgender Einsen III

Fügen Sie hier eine Bildbeschreibung ein
Leetcode 1004. Maximale Anzahl aufeinanderfolgender Einsen III

Wenn wir das Thema analysieren, können wir das sehenDie Tatsache, dass 0 umgedreht werden kann, bedeutet: 0 kann als 1 verwendet werden . Dann besteht unser Ziel darin, ein Intervall zu finden, das die größte Anzahl an Einsen enthält, und die Anzahl der Nullen in diesem Intervall darf k nicht überschreiten.

Die gewalttätige Aufzählungsmethode für diese Frage wird nicht demonstriert. Gehen wir direkt zum Schiebefenster.
Fügen Sie hier eine Bildbeschreibung ein
Fügen Sie hier eine Bildbeschreibung ein
Auf diese Weise wird unser Code übergeben
Fügen Sie hier eine Bildbeschreibung ein

class Solution {
public:
    int longestOnes(vector<int>& nums, int k) {
        int n = nums.size();
        int zero = 0;//统计0的个数
        int ret = 0;
        for(int left = 0, right = 0; right < n; right++)
        {
            //进窗口,判断是否是0
            if(nums[right] == 0)
                zero++;
            //判断0的个数是否大于k
            while(zero > k)
            {
                //判断是否是0出窗口
                if(nums[left] == 0)
                    zero--;
                left++;
            }
            //更新结果
            ret = max(ret,right-left+1);
        }
        return ret;
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

4. Mindestanzahl von Operationen, um x auf 0 zu reduzieren

Fügen Sie hier eine Bildbeschreibung ein
Leetcode 1658. Mindestanzahl von Operationen, um x auf 0 zu reduzieren

Wenn wir die Fragen lesen, können wir feststellen, dass es auf der linken Seite ist und es auf der rechten Seite schwierig ist, es zu kontrollieren.
Das Gegenteil ist der Fall, wenn es schwierig ist

  • Wir finden ein kontinuierliches Intervall im Array, entfernen den Inhalt des kontinuierlichen Intervalls im Array und der verbleibende Inhalt ist genau gleich x. Haben wir dann eine Situation gefunden?
  • Dieses kontinuierliche Intervall istSumme der Elemente im Array minus x
  • Wenn das längste kontinuierliche Intervall im Array gefunden wird, haben wir die beste Lösung gefunden.

Fügen Sie hier eine Bildbeschreibung ein
Verwenden Sie wie bei der vorherigen Frage ein Schiebefenster.

Fügen Sie hier eine Bildbeschreibung ein

class Solution {
public:
    int minOperations(vector<int>& nums, int x) {
        int ret = 0;
        int total = 0;
        int n = nums.size();
        for(auto e : nums)
            total += e; //求数组的和
        int target = total - x; //target为要找的连续区间的总大小

        if(target < 0)//细节
            return -1;
        if(total == x)
            return n;
            
        int sum = 0;
        for(int left = 0, right = 0; right < n; right++)
        {
            sum += nums[right];//进窗口
            while(sum > target)
            {
                sum -= nums[left];
                left++;
            }
            if(sum == target)//找到目标区间,取长的一个
                ret = max(ret,right - left + 1);
        }
        if(ret == 0)
            return -1;
        else
            return n - ret;
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

5. Obstkörbe

Fügen Sie hier eine Bildbeschreibung ein
Leetcode 904.Früchte im Korb

Dieser Titel ist so lang, was bedeutet er?

Es gibt nur zwei Körbe und es können nur zwei Arten von Früchten in den Körben sein, und sie können keine Bäume überqueren. Finden Sie die maximale Anzahl an Bäumen, an denen Sie vorbeikommen können.
Um die Länge eines kontinuierlichen Intervalls zu ermitteln, können wir ein Schiebefenster verwenden

Fügen Sie hier eine Bildbeschreibung ein
Woher weiß ich also, wie viele Früchte gerade gepflückt werden? ----Hash-Tabellenstatistiken verwenden
Fügen Sie hier eine Bildbeschreibung ein
Wir können feststellen, dass der Verbrauch bei der Verwendung von Hash-Tabellen immer noch recht hoch ist. Kann er weiter optimiert werden?

Aus den Fragenanforderungen geht hervor, dass die Kategorie kleiner als 10 ist5, sodass wir direkt ein Array + eine Variable (Datensatztyp) verwenden können.
Fügen Sie hier eine Bildbeschreibung ein

Auf diese Weise hat sich unsere Effizienz erheblich verbessert.
Fügen Sie hier eine Bildbeschreibung ein

class Solution {
public:
    int totalFruit(vector<int>& fruits) {
        int n = fruits.size();
        int ret = 0;
        int hash[100000] = { 0 };
        int kinds = 0;
        for(int left = 0, right = 0; right < n; right++)
        {
            if(hash[fruits[right]] == 0)//判断是否是新种类水果
                kinds++;

            hash[fruits[right]]++;//进窗口
            
            while(kinds > 2)//判断总类是否大于2
            {
                hash[fruits[left]]--;//出窗口
                if(hash[fruits[left]] == 0)
                    kinds--;  //去除该种类
                left++;
            }
            ret = max(ret,right - left + 1);
        }
        return ret;
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

6. Finden Sie alle Buchstabenanagramme in der Zeichenfolge

Fügen Sie hier eine Bildbeschreibung ein

Leetcode 438. Finden Sie alle Buchstabenanagramme in einer Zeichenfolge

Aus der Analyse der Frage können wir ersehen, dass es darum geht, die p-Zeichenfolge in der s-Zeichenfolge zu finden (die Reihenfolge der Zeichen in der p-Zeichenfolge kann gestört sein).
Dann können wirSuchen Sie einfach eine Teilzeichenfolge, deren Länge p entspricht und deren Zeichentyp und Anzahl der p-Zeichenfolge entsprechen.

Wir können also zwei Hash-Tabellen verwenden, um die Typen und die Anzahl der Zeichen in den Zeichenfolgen p bzw. s zu zählen.

Dann versuchen wir es zunächst mit der gewalttätigen Aufzählung:
Fügen Sie hier eine Bildbeschreibung ein
Fügen Sie hier eine Bildbeschreibung ein

Wie werden die beiden Hash-Tabellen verglichen?

  1. Verwenden Sie zunächst hash1, um die Anzahl der Zeichentypen in der p-Zeichenfolge aufzuzeichnen.
  2. Durchlaufen Sie s und finden Sie die Teilzeichenfolge, deren Länge p entspricht, und Hash2 speichert das aktuelle Zeichen.
    a. Wenn hash2[right] &lt;= hash1[right], erhöht sich die Anzahl der „gültigen Zeichen“.
    b. Wenn hash2[right] &gt; hash2[right], bleibt der Typ der „gültigen Zeichen“ unverändert

Fügen Sie hier eine Bildbeschreibung ein
Die gewaltsame Aufzählungsmethode ist zwar machbar, aber relativ ineffizient.
Können wir ein Schiebefenster verwenden, da wir nach einem kontinuierlichen Intervall suchen?Lass es uns versuchen
Fügen Sie hier eine Bildbeschreibung ein
Auf diese Weise wird unsere Effizienz viel höher sein
Fügen Sie hier eine Bildbeschreibung ein

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        vector<int> ret;
        int len = p.size();
        int hash1[128] = { 0 };
        for(auto e: p)
            hash1[e]++;//统计p串的种类
        int n = s.size();
        int hash2[128] = { 0 };//统计子串种类
        int kinds = 0;
        int tmp = 0;
        for(int left = 0,right = 0; right < n ; right++)
        {
            hash2[s[right]]++; //进窗口
            tmp++;//子串长度增加
            if(hash2[s[right]] <= hash1[s[right]])
                kinds++;//"有效字符"种类增加
            if(tmp > len)
            {
                if(hash2[s[left]] <= hash1[s[left]])
                    kinds--;//left位置为“有效字符”,种类减少
                hash2[s[left]]--;
                tmp--;
                left++;
            }
            if(tmp == len)
                if(kinds == len)
                    ret.push_back(left);
        }
        return ret;
        
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

7. Verketten Sie Teilzeichenfolgen aller Wörter

Fügen Sie hier eine Bildbeschreibung ein

Leetcode 30. Teilzeichenfolgen aller Wörter verketten

Diese Frage ähnelt der vorherigen Frage, außer dass die Zeichen durch Zeichenfolgen ersetzt werden. Daher verwenden wir weiterhin: Schiebefenster + Hash-Tabelle, um das Problem zu lösen.
Fügen Sie hier eine Bildbeschreibung ein

Schritte zur Problemlösung:

  1. Verwenden Sie zunächst hash1, um die Zeichenfolgentypen in Wörtern zu zählen
  2. Verwenden Sie ein Schiebefenster (da die Länge der Zeichenfolge fest ist, können Sie die Länge direkt überspringen)
    a. Definitionsanfang: links, rechts
    b. Wenn die Zeichenfolge in das Fenster gelangt, müssen Sie sie mit der Zeichenfolgenfunktion substr ausschneiden.
    c. Bestimmen Sie die Art der Zeichenfolge
    Ob das Fenster verlassen werden soll
    Ergebnisse aktualisieren

Fügen Sie hier eine Bildbeschreibung ein
Bei der Analyse seiner Testfälle wurde jedoch festgestellt, dass nur ein Teil des Codes bestanden hat.
Fügen Sie hier eine Bildbeschreibung ein
Das ist der Code!

Fügen Sie hier eine Bildbeschreibung ein

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        vector<int> ret;
        unordered_map<string,int> hash1;
        for(auto& e : words)
            hash1[e]++;//统计要求的字符串种类
        int n = s.size();
        int len = words[0].size();//固定的字符串长度
        int m = words.size();//数组长度
        for(int i=0; i < len; i++)
        {
            unordered_map<string,int> hash2;
            int count = 0;
            for(int left = i, right = i; right <= n -len; right+= len)
            {
                string in = s.substr(right,len);//裁剪字符串
                hash2[in]++;//进窗口
                if(hash2[in] <= hash1[in])
                    count++;//统计当前字符串数量
                if(right - left + 1 > m*len)//判断字符串数量是否大于字符数组
                {
                    string out = s.substr(left,len);
                    if(hash2[out] <= hash1[out])
                        count--;
                    hash2[out]--;//出窗口
                    left += len;
                }
                if(count == m)
                    ret.push_back(left);
            }
        }
        return ret;
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

8. Minimaler abdeckender Teilstring

Fügen Sie hier eine Bildbeschreibung ein
Leetcode 76. Minimaler abdeckender Teilstring

Die Lösung dieser Frage ähnelt der vorherigen Frage. Es wird auch nach allen Zeichen der Zeichenfolge t in der Zeichenfolge s gesucht, mit der Ausnahme, dass die kleinste der gefundenen Zeichenfolgen zurückgegeben wird.

Wir verwenden immer noch Schiebefenster + Hash-Tabelle, um das Problem zu lösen.

  1. Verwenden Sie hash1, um den Typ und die Anzahl der Zeichen im T-String zu zählen
  2. Schiebefenster: Startposition links, rechts definieren
    a. Betreten Sie das Fenster
    b. Bestimmen Sie, ob ein Fenster angezeigt wird
    c. Aktualisieren Sie das Ergebnis (Sie müssen nur die Startposition und Länge der Zeichenfolge aufzeichnen und schließlich die Teilzeichenfolge abschneiden).

Fügen Sie hier eine Bildbeschreibung ein

Notieren Sie einfach die Startposition und Länge der Zeichenfolge im Code, und schon ist unser Code beendet.

Fügen Sie hier eine Bildbeschreibung ein

class Solution {
public:
    string minWindow(string s, string t) {
        int hash1[128] = { 0 };
        for(auto e : t)
            hash1[e]++;//统计t串的字符种类和数量
        int m = t.size();
        int n = s.size();
        int hash2[128] = { 0 };
        int count = 0;
        int start = 0;
        int len = INT_MAX;
        for(int left = 0, right = 0; right < n; right++)
        {
            hash2[s[right]]++;//进窗口
            if(hash2[s[right]] <= hash1[s[right]])//是否更新有效字符
                count++;
            while(count >= m)//是否出窗口
            {
                if(count == m)//找到符合种类的子串
                {
                    //取长度小的一个
                    int curlen = right - left + 1;
                    if(curlen < len)
                    {
                        start = left;
                        len = curlen;
                    }
                }
                //出窗口
                if(hash2[s[left]] <= hash1[s[left]])
                    count--;
                hash2[s[left]]--;
                left++;
            }
        }
        if(len == INT_MAX)
            return "";
        else
            return s.substr(start,len);
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42