算法:單詞縮寫

原題單詞縮寫c++

給出一組 n 個不一樣的非空字符串,您須要按如下規則爲每一個單詞生成 最小 的縮寫。數組

  • 從第一個字符開始,而後加上中間縮寫掉的字符的長度,後跟最後一個字符。
  • 若是有衝突,就是多個單詞共享相同的縮寫,使用較長的前綴,而不是僅使用第一個字符,直到使單詞的縮寫的映射變爲惟一。 換句話說,最終獲得的縮寫不能映射到多個原始單詞。
  • 若是縮寫不會使單詞更短,則不進行縮寫,保持原樣。

審題

  1. 首先這題的有個地方說得不清楚,就是解決衝突的方式。好比iabcx和idefx衝突了,由於縮寫都是i3x,這時須要把這兩個都加長前綴,變成ia2x和id2x。重點在於二者都變化。應用到整個集合裏,當有其餘單詞和你衝突時,你就要增大前綴,知道沒有單詞和你衝突。
  2. 我看了下題目的標籤,裏有個排序,順着這個思路想了下:
  • 只有後綴相同的單詞以前會可能衝突,由於後綴必定保留bash

  • 只有長度相同的會衝突。證實:若是長度不一樣的單詞縮寫到長度相同,那麼縮寫掉部分的長度確定不一樣,那麼中間的數字確定不一樣,那麼這兩個縮寫確定不一樣,不會衝突。ide

  因此只有長度相同且後綴相同的單詞纔會衝突,因此把這些可能衝突的分到一個組裏。ui

  1. 通過2的處理,同一個組裏,都是有衝突隱患的單詞。對於某一個單詞,要保留多長的前綴才能夠呢?它和組內的其餘單詞比較共同前綴,假如最長共同前綴爲k,那麼它就保留到k+1,由於到k+1的這一段,全部其餘的都會跟它呈現出不一樣。若是直接照着這個直接實現,複雜度是O(n^2),n是組內的單詞個數,有沒有辦法直接找到最長共同前綴呢?直接使用字符串的排序,字符串自己的比較方法是從前日後逐個比較字符,因此具備更多共同前綴的單詞會貼到一塊兒。
  2. 證實以下:

  假設排序後單詞k和k-1的共同前綴長度是x,單詞k和k+1的共同長度是y, 若是最長共同前綴不是x或y,那麼存在一個單詞t,它不在k的兩邊,且共同前綴z知足:z>x,z>y。spa

  單詞k+1在k的後面,且共同前綴是y,說明:k+1[y+1]>k[y+1];同理對於k-1有:k-1[x+1]<k[x+1]。而z>x且z>y,那麼這兩個式子對於t也是成立的,也就是在排序後t會在k-1和k+1之間,而這時不可能的,因此出現錯誤假設不成立,不存在這樣的t。code

  簡單說,公共前綴越長,排序後越靠近。因此每一個單詞只須要取左右相鄰單詞的共同前綴做爲參考便可。排序

代碼:

//對單詞排序,按長度、後綴和單詞自己的前後順序
//這樣長度相同、後綴相同的單詞會分到一塊兒
bool wordPairCmp(pair<string, int>& wp1, pair<string, int> &wp2){
    int result = (int)wp1.first.length()-(int)wp2.first.length();
    if (result!=0) return result<0;
    result = wp1.first.back()-wp2.first.back();
    if (result!=0) return result<0;
    
    return wp1.first.compare(wp2.first)<0;
}

//求共同前綴的長度
inline int prefixLapCount(string &s1, string &s2){
    int c = 0;
    while (s1[c] == s2[c]) {
        c++;
    }
    return c;
}

//把一個單詞按指定前綴長度縮寫
inline void wordAbb(string &originalWord, int prefixCount){
    int len = (int)originalWord.length();
    int cut = len-prefixCount-1;
    if (cut<=1) {
        return;
    }
    
    int destLen = prefixCount+1+log10(cut)+1;
    int preIdx = len-cut-2;
    string abb(destLen,' ');
    
    for (int i = 0; i<=preIdx; i++) {
        abb[i] = originalWord[i];
    }
    abb[destLen-1] = originalWord.back();
    
    //中間縮寫的數字部分,沒有使用atoi等方法而是直接實現,效率會快很對
    for (int i = destLen-2; i>preIdx; i--) {
        abb[i] = cut%10+'0';
        cut = cut/10;
    }
    
    originalWord = abb;
}

vector<string> wordsAbbreviation(vector<string> &dict) {
    
    //使用pair的緣由是爲了記錄單詞在原數組裏的索引位置,這樣排序後,還能夠再重置到輸入時的順序
    vector<pair<string, int>> wordPairs;
    int i = 0;
    for (auto &w : dict){
        wordPairs.push_back({w,i});
        i++;
    }
    sort(wordPairs.begin(), wordPairs.end(), wordPairCmp);
    
    int size = (int)wordPairs.size();
    int preLapCount = 0; //和前一個重疊的字符個數
    for (int i = 0; i<size; i++) {
        
        int nextLapCount = 0;
        //由於沒有使用分組,即沒有用多維數組,而是單個數組,因此須要作先後斷定,長度不一樣或者後綴不一樣,則共同前綴就不考慮了,直接是0.
        //這裏會影響效率,由於大部分比較都是無心義的或者說跟排序的工做有重複
        if (i<size-1 &&
            wordPairs[i].first.length() == wordPairs[i+1].first.length() &&
            wordPairs[i].first.back() == wordPairs[i+1].first.back()) {
            nextLapCount = prefixLapCount(wordPairs[i].first, wordPairs[i+1].first);
        }
        
        wordAbb(wordPairs[i].first, max(preLapCount, nextLapCount)+1);
        preLapCount = nextLapCount;
    }
    
    for (auto &p : wordPairs){
        dict[p.second] = p.first;
    }
    
    return dict;
}
複製代碼
相關文章
相關標籤/搜索