鏈接:
題目描述:
Rabbit得到了一個字符串,她的好朋友xxx可以給這個字符串施加一次魔法。
魔法可以選擇字符串的任一位置,並將該位置後面的所有字符水平拼接到串首。
例如:對於字符串abcde,可以通過施加魔法得到cdeab。
如果xxx通過施加魔法將字符串的字典序變得嚴格比之前的小,那麼他將拿走這一字符串。
Rabbit想知道自己的字符串會不會被xxx拿走。
思路:
這次練習賽的簽到題,最小表示法裸題,知識點不難,以前也有見過,只是不太常見生疏了,拿出來理一下思路,順便回顧一下。
算法:
求解環形字符串從哪個位置開始字典序最小(大)時,暴力算法枚舉起點,再遍歷一次字符串,複雜度O(n^2),實在不能令人滿意。
最小(大)表示法通過引入i,j,l三個變量,其中i,j充當指針作用,用來表示"從位置i(j)開始的字符串字典序最小(大)",l則是從位置i和位置j開始的字符串,有連續長度爲l位相同。
即:str[i]->str[i+l] == str[j]->str[j+l]
以最小表示法爲例,一開始,令i = 0,j = 1,l根據實際情況得出,每次計算出l之後會有以下幾種情況:
①l == len:意爲從第i位開始的串和第j位開始的環形串字典序相同(如"aaaaa"),這時直接返回結果即可(i/j均可,不過一般除了SPJ答案唯一,一般都要比較小的)
思考:爲什麼從環形字符串兩個不同位置開始的字符串完全相同,從這兩個位置開始的字符串字典序就最小?
因爲出現這種情況的串一定是所有字符均相同的串。
假設該環形字符串所有字符不全相同,則從兩個不同位置i,j開始的串裏,該字符所在的位置不同。
舉例:aaabaaaa
從0開始的串:aaabaaaa
從1開始的串:aabaaaaa
與我們的假設——“從兩個位置開始的字符串相同”相違背。
②str[i+l+1] > str[j+l+1]:從i - > max(i+l,j-1)裏面不可能產生最佳答案。
爲什麼不能在[i,i+l]裏產生?
假設s1 s2 s3和s7 s8 s9相同,s4 > s10
那麼從s2開始的串s2 s3 s4和s8 s9 s10相比會面臨相同的問題:s4 > s10,這一問題沒有改變。
爲什麼不能在[i,j-1]裏產生?
i和j都是從小往大增上去的,也就是說j已經走過[i,j-1]這段路並證明這段路非最優了。
令i = max(i+l+1,j+1)
③str[i+l+1] < str[j+l+1]:從j -> max(j+l,i-1)裏面不可能產生最佳答案。
令j = max(j+l+1,i+1)
最後,當i或者j >= len的時候,就可以結束循環了。
這時候返回i和j裏面比較小的一個就是答案了。
爲什麼是比較小的一個?
因爲每次循環改變的i,j都是不優的。考慮最後一次i和j的變化,是它們的變大使得i < len && j < len這個條件不成立了,那就是意味着 < len的一個是最優答案。
代碼:
//最小表示法、最大表示法 O(n) 返回從某一位開始的字典序序列最小/最大
int getMin(const char& str){
int len = strlen(str);
int i = 0,j = 1;
while (i < len && j < len){
int l = 0;
while(l < len)
if(str[(i + l) % len] != str[(j + l) % len])break;
else l++;
if(l >= len)break;
if(str[(i + l) % len] > str[(j + l) % len]){
if (i + l + 1 > j)i = i + l + 1;
else i = j + 1;
}
else if (j + l + 1 > i) j = j + l + 1;
else j = i + 1;
}
return i < j ? i : j;
}
int getMax(int len) {
int i = 0, j = 1, k = 0;
while (i < len && j < len && k < len) {
int t = str[(i + k) % len] - str[(j + k) % len];
if (!t) k++;
else {
if (t > 0) {
if (j + k + 1 > i) j = j + k + 1;
else j = i + 1;
}
else if (i + k + 1 > j) i = i + k + 1;
else i = j + 1;
k = 0;
}
}
return i < j ? i : j;
}
其他應用場景:
最小(大)表示法可以將一個環形字符串唯一確定地表示(從開始位置跑一遍就是了),可據此定義環形字符串的大小比較運算。