計蒜客 - 旋轉數字
蒜頭君發現了一個很好玩的事情,他對一個數作旋轉操作,把該數的最後的數字移動到最前面。比如,數 123123 可以得到 312, 231, 123312,231,123,這樣就可以得到很多個數。
現在,蒜頭君的問題是這些數中,有多少個不同的數小於原數,多少個等於原數,多少個大於原數。
旋轉中可能會出現前導零,兩數比較的時候可以忽略前導零的影響。
輸入格式
輸入一個整數 。
341
輸出格式
答案在一行中輸出三個整數,分別是小於 N,等於 N,大於 N 的個數,中間以空格隔開。
1 1 1
這道題是一道利用拓展 KMP 算法的好題目。
我們一點一點來剖析這道題。
第一個問題,怎麼枚舉出所有旋轉得到的數字?
把數字收尾相接,然後從第 0 個位置到第 length 個位置,每次往後取 length 個長度的字符,就可以遍歷出所有可能的 length 個數字。
char num[MAX_LEN];
scanf("%s", num);
int len = strlen(num);
char* numnum = duplicate(num);
for (int i = 0; i < len; i++) {
for (int j = i; j < i + len; j++) {
printf("%c", numnum[j]);
}
printf("\n");
}
char* concat(const char* s1, const char* s2) {
int len1 = strlen(s1);
int len2 = strlen(s2);
char* s3 = (char*)malloc(sizeof(char) * (len1 + len2 + 1));
strncpy(s3, s1, len1);
strncpy(s3 + len1, s2, len2);
s3[len1 + len2] = '\0';
return s3;
}
char* duplicate(const char* s) {
return concat(s, s);
}
第二個問題,怎麼比較大小?
由於數字非常大,我們不可能每次都轉成整型以後去比較大小,而利用字符串相等來比較數字之間的大小,是一種常用的做法。
首先讓我們考慮怎麼比較兩個數字相等,顯然,那就是這兩個字符串相等,即 strcmp(s1, s2) == 0
,對應到 KMP 算法中,那就是 next[s[i]] = length
。
parseInt(s1) === parseInt(s2)
// 等價於
s1 === s2
當然,這裏忽略了前導零,因爲 s1 = '001'
和 s2 = '01'
,顯然 parseInt(s1) === parseInt(s2)
是成立的,但是 s1 === s2
是不成立的。
可是,由於題目要求的是不同的數字,所以我們不需要考慮重複數字,那麼,在這種情況下,與原數相等的數字就只有 1 個了。
所以我們只需要比較大於,或者小於原數的數字。
給定兩個字符串 s1
和 s2
,如果已知他們開始的 x
個字符都相等,如何判斷它們的大小?只需要比較第 x + 1
個位置上的字符的大小就可以了。
而前 x
個字符都相等,這一點 next[]
數組已經幫我們做好了,所以我們只需要比較 numnum[i + next[i]]
和 numnum[1 + next[i]]
的大小。
int greater = 0;
int equal = 0;
int less = 0;
for (int i = 1; i <= len; i++) {
if (next[i] == len * 2) {
equal++;
} else if (numnum[i + next[i]] < numnum[1 + next[i]]) {
less++;
} else {
greater++;
}
}
printf("%d %d %d", less, equal, greater);
下面還剩兩個問題,第一個,如何去除循環,第二個,如何解決前導零。
在解決這兩個問題之前,我們先來看一組樣例。
對於 123123 這個數,我們期望得到的數字是 123123、231231、312312 這 3 個,而不是將 123123 變成 123123123123 之後枚舉出來的 6 個。
所以,能不能通過將數字只複製最小的循環節來完成?
答案是顯然的。
求循環節的過程之前已經介紹過了,就是利用 n - next[n]
即可,那麼我們只需複製這樣一小節拼在最後就可以了。
// 求出字符串 t 的循環節長度
int getLoop(const char *t, int length) {
int *next = (int *) malloc(sizeof(int) * (length + 1));
next[1] = 0;
for (int i = 2; i <= length; i++) {
int j = next[i - 1];
while (t[j + 1] != t[i] && j > 0) {
j = next[j];
}
if (t[j + 1] == t[i]) {
next[i] = j + 1;
} else {
next[i] = 0;
}
}
return length - next[length];
}
char *duplicate(const char *s) {
int len = strlen(s + 1);
int loop = getLoop(s, len);
char *ss = (char *) malloc(sizeof(char) * (len + loop + 2));
strncpy(ss + 1, s + 1, len);
strncpy(ss + 1 + len, s + 1, loop);
ss[1 + len + loop] = '\0';
return ss;
}
那麼與之對應的,for (int i = 1; i <= len; i++)
中的 len
,就應該換成循環節的長度 getLoop(num + 1, strlen(num + 1))
。
這樣一來,重複數字的問題也就自然而然地被解決了。前導零也不需要特別處理了。
但是這麼做的話,123123
可以被正常處理,123123123
也可以得到正確的結果,因爲對於他們來說,循環節都是 123
,只需要拼接一段循環節到最後就可以了。
然而,12312312312
求出的循環節 123
可不能被簡單地拼接到最後,因爲對於這個數字來說,12312312312
纔是需要被拼接的。
所以我們需要判斷一下,最後一個是不是真的完整的循環節。
int loop = length - next[length];
if (next[length] % loop != 0) {
return length;
} else {
return loop;
}
由此,可以得到完整代碼:
#include <bits/stdc++.h>
//#define DEBUG_USE_ONLY
#define MAX_LEN 100007
void print(const char *t, const int *next, int n) {
for (int i = 1; i <= n; i++) {
printf("%c%c", t[i], i == n ? '\n' : '\t');
}
for (int i = 1; i <= n; i++) {
printf("%d%c", next[i], i == n ? '\n' : '\t');
}
}
// 求出字符串 t 的循環節長度
int getLoop(const char *t, int length) {
int *next = (int *) malloc(sizeof(int) * (length + 1));
next[1] = 0;
for (int i = 2; i <= length; i++) {
int j = next[i - 1];
while (t[j + 1] != t[i] && j > 0) {
j = next[j];
}
if (t[j + 1] == t[i]) {
next[i] = j + 1;
} else {
next[i] = 0;
}
}
int loop = length - next[length];
#ifdef DEBUG_USE_ONLY
print(t, next, length);
printf("[%d]\n", loop);
#endif
if (next[length] % loop != 0) {
return length;
} else {
return loop;
}
}
char *duplicate(const char *s, int len, int loop) {
char *ss = (char *) malloc(sizeof(char) * (len + loop + 2));
strncpy(ss + 1, s + 1, len);
strncpy(ss + 1 + len, s + 1, loop);
ss[1 + len + loop] = '\0';
return ss;
}
int *getNext(const char *t, int n) {
int *next = (int *) malloc(sizeof(int) * (n + 1));
next[1] = n;
int p = 1;
while (p < n && t[p] == t[p + 1]) p++;
next[2] = p - 1;
int k = 2, l;
for (int i = 3; i <= n; i++) {
p = k + next[k] - 1;
l = next[i - k + 1];
if (i + l <= p) next[i] = l;
else {
int j = p - i + 1;
if (j < 0) j = 0;
while (i + j <= n && t[i + j] == t[j + 1]) j++;
next[i] = j;
k = i;
}
}
return next;
}
int main() {
char num[MAX_LEN];
scanf("%s", num + 1);
int len1 = strlen(num + 1);
int loop = getLoop(num, len1);
char *numnum = duplicate(num, len1, loop);
int len2 = strlen(numnum + 1);
int *next = getNext(numnum, len2);
#ifdef DEBUG_USE_ONLY
print(numnum, next, len2);
#endif
int greater = 0;
int equal = 0;
int less = 0;
for (int i = 1; i <= loop; i++) {
#ifdef DEBUG_USE_ONLY
for (int j = i; j < i + len1; j++) {
printf("%c", numnum[j]);
}
printf(", next = %d, %c - %c\n", next[i], numnum[i + next[i]], numnum[1 + next[i]]);
#endif
if (next[i] == len2) {
equal++;
} else if (numnum[i + next[i]] < numnum[1 + next[i]]) {
less++;
} else {
greater++;
}
}
printf("%d %d %d", less, equal, greater);
return 0;
}
歡迎關注我的個人博客以閱讀更多優秀文章:凝神長老和他的朋友們(https://www.jxtxzzw.com)
也歡迎關注我的其他平臺:知乎( https://s.zzw.ink/zhihu )、知乎專欄( https://s.zzw.ink/zhuanlan )、嗶哩嗶哩( https://s.zzw.ink/blbl )、微信公衆號( 凝神長老和他的朋友們 )