leetcode *1371. 每個元音包含偶數次的最長子字符串(待研究)

【題目】*1371. 每個元音包含偶數次的最長子字符串

*1371. 每個元音包含偶數次的最長子字符串
*1248. 統計「優美子數組」

給你一個字符串 s ,請你返回滿足以下條件的最長子字符串的長度:每個元音字母,即 ‘a’,‘e’,‘i’,‘o’,‘u’ ,在子字符串中都恰好出現了偶數次。

示例 1:

輸入:s = "eleetminicoworoep"
輸出:13
解釋:最長子字符串是 "leetminicowor" ,它包含 e,i,o 各 2 個,以及 0 個 a,u 。

示例 2:

輸入:s = "leetcodeisgreat"
輸出:5
解釋:最長子字符串是 "leetc" ,其中包含 2 個 e 。

示例 3:

輸入:s = "bcbcbc"
輸出:6
解釋:這個示例中,字符串 "bcbcbc" 本身就是最長的,因爲所有的元音 a,e,i,o,u 都出現了 0 次。

提示:
1 <= s.length <= 5 x 10^5
s 只包含小寫英文字母。

【解題思路1】異或運算 前綴和 + 狀態壓縮

直接解讀官方優化的部分

class Solution {
    public int findTheLongestSubstring(String s) {
        int n = s.length();
        int[] pos = new int[1 << 5];//5個元音字母,就是00000-11111,2^5種情況,或者叫狀態
        Arrays.fill(pos, -1);//用-1填充是怕00000這種情況,避免混淆
        int ans = 0, status = 0;
        pos[0] = 0;
        for (int i = 0; i < n; i++) {
            char ch = s.charAt(i);
            /*-------------開始----------------*/
            //在這裏主要是用當前字符去更新上一個子串的狀態,因爲當前這個字符可能是元音字符或者不是
            //用異或的原因就是,我們只關心奇偶性,異或相同爲0,不同爲1,那麼只要將上一個狀態對應位與
            //1 << (0-4)一下就可以了。也就是第一次來是奇數,那麼第二次就是偶數,第三次是奇數...
            if (ch == 'a') {
                status ^= (1 << 0);
            } else if (ch == 'e') {
                status ^= (1 << 1);
            } else if (ch == 'i') {
                status ^= (1 << 2);
            } else if (ch == 'o') {
                status ^= (1 << 3);
            } else if (ch == 'u') {
                status ^= (1 << 4);
            }
            /*--------------結束---------------*/
            //得到一個新的狀態值,如果這個狀態值作索引對應位置的值大於0,
            //那麼就說明第一次出現該值的位置到當前位置所對應的字符串就是當前
            //符合要求的子串,這是因爲如果在i的位置和j的位置對應的狀態值相等,
            //那麼這兩個子串的奇偶性肯定相等,既然奇偶性相同了,那麼中間範圍對應的子串就是我們要求的
            //同時我們要和之前符合要求的子串比較一下長度,因爲我們要取最長的。
            if (pos[status] >= 0) {
                ans = Math.max(ans, i + 1 - pos[status]);
            } else {
                //pos只存放每一個狀態值第一個出現的位置。
                pos[status] = i + 1;
            }
        }
        return ans;
    }
}
class Solution {
    public int findTheLongestSubstring(String s) {
        int res = 0;
        char[] c = s.toCharArray();
        // Key爲前i項的前綴和,value爲i
        HashMap<Integer,Integer> map = new HashMap<>();
        int[] dp = new int[c.length+1];
        dp[0] = 0;
        for(int i = 0; i < c.length; i++) {
            // 當遇到元音時進行異或運算,兩個相同字母異或運算爲0
            if( c[i] == 'a' ||
                c[i] == 'e' ||
                c[i] == 'i' ||
                c[i] == 'o' ||
                c[i] == 'u')
                dp[i+1] = dp[i] ^ c[i];
            // 如果遇到非元音字母則保持前項結果
            else
                dp[i+1] = dp[i];
            // 如果前項和爲0,則說明此字串爲滿足題意要求的子串
            if (dp[i+1] == 0) {
                res = i + 1;
                continue;
            }
            // 如果當前map中存在當前的前綴和,則當前前綴和與前部前綴和異或運算也爲0
            if(map.containsKey(dp[i+1])) {
                res = Math.max(res,i - map.get(dp[i+1]));
            }
            // 若不含當前字串前綴和,將其前綴和作爲key,i作爲value加入map中
            else
                map.put(dp[i+1],i);
        }
        return res;
    }
}
public int findTheLongestSubstring(String s) {
    char[] arr = s.toCharArray();
    int even = 0b0, max = 0;
    int[] dp = new int[1 << 5]; // "各種"情況,首次出現的索引
    Arrays.fill(dp, -1);
    dp[even] = 0; // 現在是完美平衡
    for (int i = 0; i < arr.length; i++) {
        char c = arr[i];
        if (c == 'a') even ^= 1;
        else if (c == 'e') even ^= (1 << 1);
        else if (c == 'i') even ^= (1 << 2);
        else if (c == 'o') even ^= (1 << 3);
        else if (c == 'u') even ^= (1 << 4);
        if (dp[even] == -1) dp[even] = i + 1;
        else max = Math.max(max, i + 1 - dp[even]); // 計算距離,首次出現這種情況的索引
    }
    return max;
}

【解題思路2】動態規劃

  • 我們把dp[i]作爲以字符串i位作爲結尾的、滿足要求的最長子串長度
  • 顯然目前的dp[i]沒辦法進行遞推,因爲遇到元音字母可能會改變後面的狀態,因此加入一維狀態,記錄元音字母情況
  • 將aeiou分別對應到二進制的某一位,這樣可以生成一個32以內的int,用這個int來表示另一個狀態:當前對應的元音aeiou是奇數還是偶數,原有的dp[i]升級,成爲dp[i][j],其中j就是這個int
  • 那麼很容易想得到狀態轉移方程:
    如果i不是元音,那麼dp[i][j]=dp[i-1][j]+1
    如果i是元音,那麼找到它對應的位,假設是x,那麼dp[i][j] = dp[i-1][j xor x]
  • 掃描所有的dp[i][0],其最大值就是我們想要的答案
class Solution {
    public int findTheLongestSubstring(String s) {
        char[] chars = new char[]{'a', 'e', 'i', 'o', 'u'};
        int[] nums = new int[]{16, 8, 4, 2, 1};
        int[][] dp = new int[s.length() + 1][32];
        for (int i = 0; i <= s.length(); i++) for (int j = 0; j < 32; j++) dp[i][j] = -1;
        dp[0][0] = 0;
        int result = Integer.MIN_VALUE;
        for (int i = 0; i < s.length(); i++) {
            boolean found = false;
            for (int j = 0; j < chars.length; j++)
                if (chars[j] == s.charAt(i)) {
                    found = true;
                    for (int k = 0; k < 32; k++) if (dp[i][k] != -1) dp[i + 1][k ^ nums[j]] = dp[i][k] + 1;
                    dp[i + 1][0] = Math.max(0, dp[i + 1][0]);
                }
            if (!found) {
                dp[i + 1][0] = dp[i][0] + 1;
                for (int k = 1; k < 32; k++) if (dp[i][k] != -1) dp[i + 1][k] = dp[i][k] + 1;
            }
            result = Math.max(result, dp[i + 1][0]);
        }
        return result;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章