簡體   English   中英

計算精彩子串的數量

[英]Count number of wonderful substrings

我在一個網站上發現了以下問題。

A wonderful string is a string where at most one letter appears an odd number of times.
For example, "ccjjc" and "abab" are wonderful, but "ab" is not.
Given a string word that consists of the first ten lowercase English letters ('a' through 'j'), return the number of wonderful non-empty substrings in word. If the same substring appears multiple times in word, then count each occurrence separately.
A substring is a contiguous sequence of characters in a string.
Example 1 :
Input: word = "aba"
Output: 4
Explanation: The four wonderful substrings are  a , b , a(last character) , aba.

我試圖解決它。 我實現了一個 O(n^2) 解決方案(n 是輸入字符串長度)。 但是預期的時間復雜度是 O(n)。 我無法在 O(n) 中解決它。 我找到了以下解決方案,但無法理解。 你能幫我理解下面這個問題的 O(n) 解決方案或想出一個 O(n) 解決方案嗎? 我的 O(N^2) 方法 - 對於每個子字符串檢查它是否最多有一個奇數計數字符。 可以使用 10 個字符的數組在 O(1) 時間內完成此檢查。

class Solution {
public:
    long long wonderfulSubstrings(string str) {
      long long ans=0;
      int idx=0;  long long xorsum=0;
      unordered_map<long long,long long>mp;
      mp[xorsum]++;
      while(idx<str.length()){
         xorsum=xorsum^(1<<(str[idx]-'a'));
          // if xor is repeating it means it is having even ouccrences of all elements
          // after the previos ouccerence of xor.
         if(mp.find(xorsum)!=mp.end())
             ans+=mp[xorsum];
          mp[xorsum]++;
          // if xor will have at most 1 odd character than check by xoring with (a to j)
          // check correspondingly in the map
          for(int i=0;i<10;i++){
              long long temp=xorsum;
              temp=temp^(1<<i);
              if(mp.find(temp)!=mp.end())
                  ans+=mp[temp];
          }
          idx++;
      }
      return ans;
    }
};

代碼中有兩個主要的算法技巧,位掩碼和前綴和,如果您以前從未見過它們,可能會感到困惑。 我們先來看看這個問題是如何從概念上解決的。


對於字符串 S 的任何子字符串,我們想要計算 10 個可能字母中的每一個出現的次數,並詢問每個數字是偶數還是奇數。

例如,對於子串s = accjjc ,我們可以將其總結為:odd# a, even# b,odd# c, even# d, even# e, even# f, even# g, even# h, even#我,偶#j。 這有點長,所以我們可以使用位掩碼來總結它:對於每個字母 aj,如果計數是奇數,則輸入1如果計數是偶數,則輸入0 這為我們提供了一個 10 位二進制字符串,在我們的示例中為1010000000

您可以將其視為普通整數(或 long long,取決於整數的表示方式)。 當我們看到另一個字符時,無論是偶數還是奇數,計數都會翻轉。 在位掩碼上,這與翻轉單個位或 XOR 操作相同。 如果我們添加另一個 'a',我們可以通過將它與數字1000000000進行異或來更新位掩碼以從 'even# a' 開始。

我們想計算最多一個字符數是奇數的子串的數量。 這與計算位掩碼最多為1的子串的數量相同。 有 11 個這樣的位掩碼:十個零的字符串,並且每個字符串對於十個可能的點中的每一個都恰好有一個1 如果將這些解釋為整數,則最后十個字符串是 2 的前十次冪: 1<<0, 1<<1, 1<<2, ... 1<<9


現在,我們要在O(n)時間內計算所有子串的位掩碼。 首先,解決一個更簡單的問題:只計算所有前綴的位掩碼,並將這些計數存儲在哈希圖中。 我們可以通過從一開始就保留一個正在運行的位掩碼,並通過與該字母對應的位的異或來執行更新: xorsum=xorsum^(1<<(str[idx]-'a')) 這顯然可以在單個O(n)時間通過字符串完成。

我們如何獲得任意子串的計數? 答案是prefix-sums :任何子串中的字母數都可以表示為兩個前綴數的不同。 例如,對於s = accjjc ,假設我們想要與子字符串 'jj' 對應的位掩碼。 這個子串可以寫成兩個前綴的不同: 'jj' = 'accjj' - 'acc'

同樣,我們要減去兩個前綴字符串的計數。 然而,我們只有位掩碼告訴我們每個字母的頻率是偶數還是奇數。 在位掩碼的算術中,我們將每個位置 mod 2,因此坐標減法變為 XOR。

這意味着counts(jj) = counts(accjj) - counts(acc)變為bitmask(jj) = bitmask(accjj) ^ bitmask(acc)


還有一個問題:我描述的算法仍然是二次的。 如果在每個前綴處,我們迭代所有先前的前綴位掩碼並檢查我們的掩碼是否與舊掩碼異或是 11 個目標位掩碼之一,我們仍然有二次運行時間。 相反,您可以使用 XOR 是它自己的逆這一事實:如果a ^ b = c ,則b = a ^ c 因此,不是使用舊的前綴掩碼進行異或運算,而是與 11 個目標掩碼進行異或並添加我們看到該掩碼的次數: ans+=mp[xorsum]計算以我們當前索引結尾的子串,其位掩碼為xorsum ^ 0000000000 = xorsum 之后的循環計算位掩碼是十個目標位掩碼之一的子串。 最后,您只需添加當前的前綴掩碼即可更新計數: mp[xorsum]++

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM