簡體   English   中英

范圍內最常見的角色

[英]Most frequent character in range

我有一個字符串s長度n 什么是最有效的數據結構/算法用於查找范圍i..j最常見的字符?

字符串不會隨着時間的推移而改變,我只需要重復詢問s[i]s[i + 1] ,..., s[j]最頻繁的字符串的查詢。

一個數組,其中包含每個字符的出現次數。 在迭代字符串一次時增加相應的值。 在執行此操作時,您可以記住數組中的當前最大值; 或者,在最后查找數組中的最高值。

偽代碼

arr = [0]
for ( char in string )
   arr[char]++
mostFrequent = highest(arr)

如果希望間隔獲得有效結果,可以在序列的每個索引處構建積分分布向量。 然后通過減去j + 1和i處的積分分布,你可以從s [i],s [i + 1],...,s [j]的間隔獲得分布。

Python中的一些偽代碼如下。 我假設你的角色是字符,因此256個分發條目。

def buildIntegralDistributions(s):
    IDs=[]        # integral distribution
    D=[0]*256
    IDs.append(D[:])
    for x in s:
        D[ord(x)]+=1
        IDs.append(D[:])
    return IDs

def getIntervalDistribution(IDs, i,j):
    D=[0]*256        
    for k in range(256):
        D[k]=IDs[j][k]-IDs[i][k]
    return D

s='abababbbb'
IDs=buildIntegralDistributions(s)
Dij=getIntervalDistribution(IDs, 2,4)

>>> s[2:4]
'ab'
>>> Dij[ord('a')]  # how many 'a'-s in s[2:4]?
1
>>> Dij[ord('b')]  # how many 'b'-s in s[2:4]?
1

對數組執行單次迭代,並針對每個位置記住每個字符在該位置有多少次出現。 所以像這樣:

"abcdabc"

對於索引0:

count['a'] = 1
count['b'] = 0
etc...

索引1:

....
count['a'] = 1
count['b'] = 1
count['c'] = 0
etc...

索引2:

....
count['a'] = 1
count['b'] = 1
count['c'] = 1
....

等等。 索引6:

....
count['a'] = 2
count['b'] = 2
count['c'] = 2
count['d'] = 1
... all others are 0

計算此數組后,您可以在一個間隔(i,j)中以恆定時間獲得給定字母的出現count[j] - count[i-1] - 只需計算count[j] - count[i-1] (小心這里i = 0 !) 。

因此,對於每個查詢,您將不得不迭代所有字母而不是間隔中的所有字符,因此不會迭代超過10 ^ 6個字符,您將只傳遞最多128個(假設您只有ASCII符號)。

缺點 - 您需要更多內存,具體取決於您使用的字母大小。

您需要根據空間和時間復雜度來指定算法要求。

如果你堅持O(1)空間復雜性,只需排序(例如,如果沒有可用的自然比較運算符,則使用位的字典排序)並計算最高元素的出現次數將給出O(N log N)時間復雜度。

如果你堅持O(N)時間復雜度,請使用@Luchian Grigore的解決方案,該解決方案也需要O(N)空間復雜度(對於K -letter字母表, O(K) )。

string="something"
arrCount[string.length()];

每次訪問字符串后調用freq()

freq(char accessedChar){
arrCount[string.indexOf(x)]+=1
}

獲取最頻繁的char調用string.charAt(arrCount.max())

假設字符串是常量,並且將不同的ij傳遞給查詢出現。

如果你想最小化處理時間,你可以做一個

struct occurences{
    char c;
    std::list<int> positions;
};

並為每個字符保留std::list<occurences> 為了快速搜索,您可以保持訂單的positions

如果你想最小化內存,你可以保持一個遞增的整數並循環通過i .. j

如所建議的,最節省時間的算法是將每個字符的頻率存儲在陣列中。 但請注意,如果您只是使用字符索引數組,則可能會調用未定義的行為。 也就是說,如果您正在處理包含0x00-0x7F范圍之外的代碼點的文本,例如使用UTF-8編碼的文本,最多可能會出現分段違規,最壞情況下堆棧數據損壞:

char frequncies [256] = {};
frequencies ['á'] = 9; // Oops. If our implementation represents char using a
                       // signed eight-bit integer, we just referenced memory
                       // outside of our array bounds!

正確解釋此問題的解決方案如下所示:

template <typename charT>
charT most_frequent (const basic_string <charT>& str)
{
    constexpr auto charT_max = numeric_limits <charT>::max ();
    constexpr auto charT_min = numeric_limits <charT>::lowest ();
    size_t frequencies [charT_max - charT_min + 1] = {};

    for (auto c : str)
        ++frequencies [c - charT_min];

    charT most_frequent;
    size_t count = 0;
    for (charT c = charT_min; c < charT_max; ++c)
        if (frequencies [c - charT_min] > count)
        {
            most_frequent = c;
            count = frequencies [c - charT_min];
        }

    // We have to check charT_max outside of the loop,
    // as otherwise it will probably never terminate
    if (frequencies [charT_max - charT_min] > count)
        return charT_max;

    return most_frequent;
}

如果要多次迭代同一個字符串,請修改上面的算法(作為construct_array )以使用std::array <size_t, numeric_limits <charT>::max () - numeric_limits <charT>::lowest () + 1> 然后在第一個for循環之后返回該數組而不是max字符,並省略找到最常用字符的算法部分。 在頂級代碼中構造一個std::map <std::string, std::array <...>> ,並將返回的數組存儲在其中。 然后將用於查找最常用字符的代碼移動到該頂級代碼中並使用緩存計數數組:

char most_frequent (string s)
{
    static map <string, array <...>> cache;

    if (cache.count (s) == 0)
        map [s] = construct_array (s);
    // find the most frequent character, as above, replacing `frequencies`
    // with map [s], then return it
}

現在,這僅適用於整個字符串。 如果要重復處理相對較小的子字符串,則應使用第一個版本。 否則,我會說你最好的選擇可能是做第二個解決方案,但將字符串分成可管理的塊; 這樣,您可以從緩存中獲取大部分信息,只需重新計算迭代器所在的塊中的頻率。

最快的是使用unordered_map或類似的:

pair<char, int> fast(const string& s) {
    unordered_map<char, int> result;

    for(const auto i : s) ++result[i];
    return *max_element(cbegin(result), cend(result), [](const auto& lhs, const auto& rhs) { return lhs.second < rhs.second; });
}

最輕的 ,內存方式,需要一個可以排序的非常量輸入,這樣可以使用find_first_not_of或類似的:

pair<char, int> light(string& s) {
    pair<char, int> result;
    int start = 0;

    sort(begin(s), end(s));
    for(auto finish = s.find_first_not_of(s.front()); finish != string::npos; start = finish, finish = s.find_first_not_of(s[start], start)) if(const int second = finish - start; second > result.second) result = make_pair(s[start], second);
    if(const int second = size(s) - start; second > result.second) result = make_pair(s[start], second);
    return result;
}

應該注意,這兩個函數都具有非空字符串的前提條件。 此外,如果字符串中的大多數字符都存在平局,則兩個函數都將返回首字母縮寫為最多的字符。

實例

暫無
暫無

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

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