Leetcode3-文字が繰り返されていない最長の部分文字列のブルートフォースソリューションからクイックソリューションへの移行の詳細な説明

質問の詳細

文字列を指定して、繰り返し文字を含まない最長の部分文字列の長さを調べてください。

例1:

入力: "abcabcbb"
出力:3
説明:文字が繰り返されていない最長の部分文字列は "abc"であるため、その長さは3です。
例2:

入力: "bbbbb"
出力:1
説明:文字が繰り返されていない最長の部分文字列は "b"であるため、その長さは1です。
例3:

入力: "pwwkew"
出力:3
説明:文字が繰り返されていない最長の部分文字列は "wke"であるため、その長さは3です。
答えは部分文字列の長さでなければならないことに注意してください。「pwke」は部分文字列ではなく部分列です。

暴力的な解決策

問題に直面したとき、あなたはまだ最初に暴力的な解決策を考えなければなりません、あるいはあなたはそれを直接改善することができます、そしてそれは簡単に問題を引き起こします。暴力的な解決策に基づいて、改善を行い、繰り返しを削除し、トリックを作成し、バグをほとんど発生させずにすばやく実行します。

まず、力ずくの解決策を考えるのは簡単です。文字列をトラバースし、各ポイントで常にトラバースし、繰り返される文字に遭遇するまで各文字をセットに追加します。
コードは次のように表示されます。

class Solution {
    
    
    public int lengthOfLongestSubstring(String s) {
    
    
        //首先暴力解法,每次用set与后面比较
        Set<Character> set = new HashSet<>();
        int maxLen =0;
        for(int i=0; i<s.length(); i++){
    
    
            int len =0;
            for(int j = i; j<s.length(); j++){
    
    
                char cur_ch = s.charAt(j);
                if(set.contains(cur_ch)){
    
    
                    break;
                }
                set.add(cur_ch);
                len++;
            }
            maxLen = Math.max(len,maxLen);
            set.clear();
        }
        return maxLen;

    }
}

時間計算量はO(n ^ 2)に達し、実行時間は108msで、15%のハハハを上回っています。
それでは、以下でそれを改善しましょう。

暴力的な解決策から迅速な解決策に改善

これは、開始ポインタと設定を同時に使用して行われます。
はっきり見えるように、写真を使って見てみましょう。
ここに画像の説明を挿入
繰り返されない文字の部分文字列であるポイントiからj-1を見つけたが、番号jが繰り返されていると仮定します。
この時点で、ブルートフォースソリューションのロジックに従う場合は、i + 1に戻り、セットをリセットしてから、1つずつクエリを続行する必要があります。

しかし、アヒルはそうする必要はありません。部分文字列でXに遭遇したことを知って、最初から直接トラバースしてから、トラバースした文字をセットから削除します。
この時点で、i + 1に戻るのではなく、次のXの次のビットに移動し、開始ポインターを前のXの次のビットに移動します。この位置から2番目のXに移動できるため、繰り返される文字があってはなりません。この時点で、多くのオーバーヘッドが節約され、比較を繰り返す必要がなくなりました。
ここに画像の説明を挿入
主なアイデアは、前にkmpアルゴリズムとマナチャーを調べること、つまり繰り返しを削除することです。
ソリューションは5ms速く、87%を上回り、スペースは77%で、悪くはありません。それはより明確な思考を持つ種類と見なされるべきです。

クイックソリューションコードの実装

最初のコメントは、暴力の使用から最終的な迅速な解決までの私の思考プロセスであり、直接無視することができます。ここで削除しないことで、後で確認するのに便利です。

public int lengthOfLongestSubstring(String s) {
    
    
        //首先暴力解法,每次用set与后面比较
        //当然了,暴力解法最慢了,所以要想一想如何改进
        //因为是关于字符串的,所以要想想能不能用manacher算法和kmp算法
        //肯定都不能用,不过有没有新的思路呢,能不能排除掉一些重复的?
        //有点难想啊,这个要是追求o(n)就真有点难
        //想到了,不用set,而用map来存储每个值的索引,这样的话,我就不用每次只移动一个
        //我只要移动到重复元素(前者)的后面一位就可以了。
        //同时,我要将所有的索引小于重复元素(前者)的全部删掉
        //思路详细的写一下,这里要有一个指针,来记录下当前的起点
        //后来起点的更新为重复元素的后一个索引
        //显然还能改进,因为这里用set会更好,你没有必要用map,因为你始终都是要删除它们的,
        //所以直接从start开始往后,找到cur_ch
        Set<Character> set = new HashSet<>();//键是值,value是索引
        int maxLen =0;
        int start = 0;
        int len;
        for(int i =0; i<s.length(); i++){
    
    
            len = i - start;
            for(int j = i; j<s.length(); j++){
    
    
                char cur_ch = s.charAt(j);
                if(set.contains(cur_ch)){
    
    //发现重复
                    //删除之前的点
                    int index = start;
                    while(s.charAt(index)!=cur_ch){
    
    
                        set.remove(s.charAt(index));
                        index++;
                    }
                    start = index+1;//重置起点
                    //更新i
                    i = j;
                    maxLen = Math.max(len,maxLen);
                    len = i-start+1;
                    break;
                }
                set.add(cur_ch);
                len++;
            }
            maxLen = Math.max(len,maxLen);
        }
        return maxLen;
    }
}

おすすめ

転載: blog.csdn.net/qq_34687559/article/details/109706853