# APCS實作題2017年10月第2題:交錯字串 (Alternating Strings) > 第1版:2023年2月23日 > 第2版:2023年6月11日,加上 C++ 程式碼 > 作者:王一哲 > 題目來源:[106年10月28日實作題第2題](https://apcs.csie.ntnu.edu.tw/wp-content/uploads/2018/12/1061028APCSImplementation.pdf) > [ZeroJudge 題目連結](https://zerojudge.tw/ShowProblem?problemid=c462) <br /> ## 題目 ### 問題描述 一個字串如果全由大寫英文字母組成,我們稱為大寫字串;如果全由小寫字母組成則稱為小寫字串。字串的長度是它所包含字母的個數,在本題中,字串均由大小寫英文字母組成。假設 k 是一個自然數,一個字串被稱為「k-交錯字串」,如果它是由長度為 k 的大寫字串與長度為 k 的小寫字串交錯串接組成。 舉例來說,「StRiNg」是一個 1-交錯字串,因為它是一個大寫一個小寫交替出現;而「heLLow」是一個 2-交錯字串,因為它是兩個小寫接兩個大寫再接兩個小寫。但不管 k 是多少,「aBBaaa」、「BaBaBB」、「aaaAAbbCCCC」都不是 k-交錯字串。 本題的目標是對於給定 k 值,在一個輸入字串找出最長一段連續子字串滿足 k-交錯字串的要求。例如 k=2 且輸入「aBBaaa」,最長的 k-交錯字串是「BBaa」,長度為 4。又如 k=1 且輸入「BaBaBB」,最長的 k-交錯字串是「BaBaB」,長度為 5。 請注意,滿足條件的子字串可能只包含一段小寫或大寫字母而無交替,如範例二。此外,也可能不存在滿足條件的子字串,如範例四。 <br /> ### 輸入格式 輸入的第一行是 k,第二行是輸入字串,字串長度至少為 1,只由大小寫英文字母組成(A~Z, a~z)並且沒有空白。 <br /> ### 輸出格式 輸出輸入字串中滿足 k-交錯字串的要求的最長一段連續子字串的長度,以換行結尾。 <br /> ### 範例一:輸入 ``` 1 aBBdaaa ``` ### 範例一:正確輸出 ``` 2 ``` <br /> ### 範例二:輸入 ``` 3 DDaasAAbbCC ``` ### 範例二:正確輸出 ``` 3 ``` <br /> ### 範例三:輸入 ``` 2 aafAXbbCDCCC ``` ### 範例三:正確輸出 ``` 8 ``` <br /> ### 範例四:輸入 ``` 3 DDaaAAbbCC ``` ### 範例四:正確輸出 ``` 0 ``` <br /> ### 評分說明 輸入包含若干筆測試資料,每一筆測試資料的執行時間限制(time limit)均為 1 秒,依正確通過測資筆數給分。其中: - 第 1 子題組 20 分,字串長度不超過 20 且 $k = 1$。 - 第 2 子題組 30 分,字串長度不超過 100 且 $k \leq 2$。 - 第 3 子題組 50 分,字串長度不超過 100,000 且無其他限制。 <br /> ### 提示 根據定義,要找的答案是大寫片段與小寫片段交錯串接而成。本題有多種解法的思考方式,其中一種是從左往右掃描輸入字串,我們需要紀錄的狀態包含:目前是在小寫子字串中還是大寫子字串中,以及在目前大(小)寫子字串的第幾個位置。根據下一個字母的大小寫,我們需要更新狀態並且記錄以此位置為結尾的最長交替字串長度。 另外一種思考是先掃描一遍字串,找出每一個連續大(小)寫片段的長度並將其記錄在一個陣列,然後針對這個陣列來找出答案。 ## Python 程式碼 ### 方法1:依序讀取字串的每個字母,記錄字母的大、小寫狀態同時計算最長的交錯字串長度 ```python= # 讀取題目要求的交錯字串長度 k 以及字串 data k = int(input()) data = input() # 若 k 等於1,則目前的字串長度 length、最長的字串長度 longest 皆設定為1,反之設定為0 if k == 1: length, longest = 1, 1 else: length, longest = 0, 0 # 讀取開頭的字母 if data[0].isupper(): uppers, lowers, capital = 1, 0, True else: uppers, lowers, capital = 0, 1, False # 讀取索引值為1到最後一個字母,有4種狀況 for i in range(1, len(data)): if data[i].isupper() and capital: # 這個字母是大寫、前一個字母也是大寫 uppers += 1 lowers = 0 if uppers == k: length += k longest = max(longest, length) if uppers > k: length = k elif data[i].islower() and capital: # 這個字母是小寫、前一個字母是大寫 if uppers < k: length = 0 uppers = 0 lowers = 1 capital = False if k == 1: length += 1 longest = max(longest, length) elif data[i].isupper() and not capital: # 這個字母是大寫、前一個字母是小寫 if lowers < k: length = 0 uppers = 1 lowers = 0 capital = True if k == 1: length += 1 longest = max(longest, length) elif data[i].islower() and not capital: # 這個字母是小寫、前一個字母也是小寫 uppers = 0 lowers += 1 if lowers == k: length += k longest = max(longest, length) if lowers > k: length = k print(longest) # 印出最長的交錯字串長度 ``` <br /> 依照提示,依序讀取字串的每個字母,記錄字母的大、小寫狀態同時計算最長的交錯字串長度。程式碼的第18 ~ 52行共有4種狀況: 1. 第19 ~ 26行:這個字母是大寫、前一個字母也是大寫,先將連續大寫字母數量 uppers 加 1,連續小寫字母數量 lowers 歸零。接下來又分為兩種狀況: 1. uppers 等於 k,將目前的交錯字串長度 length 加 k,再用 max 更新最長的交錯字串長度 longest。 2. uppers 大於 k,代表這組連續大寫字母可能是下一組交錯字串的開頭,將 length 設定為 k。 2. 第27 ~ 35行:這個字母是小寫、前一個字母是大寫,主要分為3個步驟 1. 如果在這個字母之前的連續大寫字母數量 uppers 小於 k,代表這個字母可能是新的交錯字串開頭,先將 length 歸零。 2. 將 uppers 設定為 0、lowers 設定為 1、字母大寫狀態 capital 設定為 False。 3. 如果 k 等於 1,將 length 加 1,再用 max 更新最長的交錯字串長度 longest。 3. 第36 ~ 44行:這個字母是大寫、前一個字母是小寫,主要分為3個步驟 1. 如果在這個字母之前的連續小寫字母數量 lowers 小於 k,代表這個字母可能是新的交錯字串開頭,先將 length 歸零。 2. 將 uppers 設定為 1、lowers 設定為 0、字母大寫狀態 capital 設定為 True。 3. 如果 k 等於 1,將 length 加 1,再用 max 更新最長的交錯字串長度 longest。 4. 第45 ~ 52行:這個字母是小寫、前一個字母也是小寫,先將連續小寫字母數量 lowers 加 1,連續大寫字母數量 uppers 歸零。接下來又分為兩種狀況: 1. lowers 等於 k,將目前的交錯字串長度 length 加 k,再用 max 更新最長的交錯字串長度 longest。 2. lowers 大於 k,代表這組連續小寫字母可能是下一組交錯字串的開頭,將 length 設定為 k。 ZeroJudge 測試結果,花費時間為 45 ms,使用記憶體 3.4 MB,通過測試。 <br /><br /> ### 方法2:先找出連續大寫或小寫的字母數量 ```python= # 讀取題目要求的交錯字串長度 k 以及字串 data k = int(input()) data = input() # 將連續的大寫或小寫字母數量儲存到串列 data2 data2 = [] # 讀取開頭的字母 if data[0].isupper(): uppers, lowers, capital = 1, 0, True else: uppers, lowers, capital = 0, 1, False # 讀取索引值為1到最後一個字母,有4種狀況 for i in range(1, len(data)): if data[i].isupper() and capital: # 這個字母是大寫、前一個字母也是大寫 uppers += 1 lowers = 0 elif data[i].islower() and capital: # 這個字母是小寫、前一個字母是大寫 data2.append(uppers) uppers = 0 lowers = 1 capital = False elif data[i].isupper() and not capital: # 這個字母是大寫、前一個字母是小寫 data2.append(lowers) uppers = 1 lowers = 0 capital = True elif data[i].islower() and not capital: # 這個字母是小寫、前一個字母也是小寫 uppers = 0 lowers += 1 # 加上最後一組連續大寫或小寫字母的長度 if data[-1].isupper(): data2.append(uppers) else: data2.append(lowers) # 讀取 data2 索引值為 0 的元素,若大於等於 k, # 則目前的交錯字串長度 length、最長的字串長度 longest 皆設定為k,反之設定為0 if data2[0] >= k: length = longest = k else: length = longest = 0 # 讀取 data2 索引值為 1 到最後一個元素,有5種狀況 for i in range(1, len(data2)): if data2[i-1] == k and data2[i] == k: # 前一組與這一組字串長度皆等於k length += k longest = max(longest, length) elif data2[i-1] > k and data2[i] == k: # 前一組字串長度大於k,這一組字串長度等於k length = 2*k longest = max(length, longest) elif data2[i-1] < k and data2[i] == k: # 前一組字串長度小於k,這一組字串長度等於k length = k longest = max(length, longest) elif data2[i-1] == k and data2[i] > k: # 前一組字串長度等於k,這一組字串長度大於k length += k longest = max(length, longest) length = 0 else: # 其它狀況,字串長度 length 歸零 length = 0 print(longest) # 印出最長的交錯字串長度 ``` <br /> 依照提示,先找出連續大寫或小寫的字母數量,記錄在串列 data2 之中,對應到程式碼的第6 ~ 35行,這部分的程式碼運作原則與方法1相同。接下來在第43 ~ 58行,計算交錯字串長度,有5種狀況: 1. 第44 ~ 46行:前一組與這一組字串長度皆等於 k,代表交錯字串長度還有可能會更長,先將 length 加 k,再用 max 更新最長的交錯字串長度 longest。 2. 第47 ~ 49行:前一組字串長度大於 k,這一組字串長度等於 k,代表前一組字串為交錯字串的開頭,先將 length 設定為 2\*k,再用 max 更新最長的交錯字串長度 longest。 3. 第50 ~ 52行:前一組字串長度小於 k,這一組字串長度等於 k,代表這一組字串為交錯字串的開頭,先將 length 設定為 k,再用 max 更新最長的交錯字串長度 longest。 4. 第53 ~ 56行:前一組字串長度等於 k,這一組字串長度大於 k,代表這一組字串為交錯字串的結尾,先將 length 加 k,再用 max 更新最長的交錯字串長度 longest,最後將 length 歸零。 5. 第57、58行:其它狀況,將 length 歸零。 由於這個方法比方法1多執行一個 for 迴圈,理論上執行時間較長。於 ZeroJudge 測試,花費時間為 57 ms,使用記憶體 3.5 MB,通過測試。 <br /><br /> ## C++ 程式碼 ### 方法1:依序讀取字串的每個字母,記錄字母的大、小寫狀態同時計算最長的交錯字串長度 ```cpp= #include <iostream> #include <string> using namespace std; int main(int argc, char* argv[]) { // 由標準輸入讀取題目要求的交錯字串長度 k 以及字串 data int k; cin >> k; string data; cin >> data; // 若 k 等於1,則目前的字串長度 length、最長的字串長度 longest 皆設定為1,反之設定為0 int length, longest; if (k == 1) { length = 1; longest = 1; } else { length = 0, longest = 0; } // 讀取開頭的字母 int uppers, lowers, capital; if (isupper(data[0])) { uppers = 1; lowers = 0; capital = 1; } else { uppers = 0; lowers = 1; capital = 0; } // 讀取索引值為1到最後一個字母,有4種狀況 for(int i=1; i<data.length(); i++) { if (isupper(data[i]) && capital == 1) { // 這個字母是大寫、前一個字母也是大寫 uppers++; lowers = 0; if (uppers == k) { length += k; longest = max(longest, length); } if (uppers > k) length = k; } else if (islower(data[i]) && capital == 1) { // 這個字母是小寫、前一個字母是大寫 if (uppers < k) length = 0; uppers = 0; lowers = 1; capital = 0; if (k == 1) { length++; longest = max(longest, length); } } else if (isupper(data[i]) && capital == 0) { // 這個字母是大寫、前一個字母是小寫 if (lowers < k) length = 0; uppers = 1; lowers = 0; capital = 1; if (k == 1) { length++; longest = max(longest, length); } } else if (islower(data[i]) && capital == 0) { // 這個字母是小寫、前一個字母也是小寫 uppers = 0; lowers++; if (lowers == k) { length += k; longest = max(longest, length); } if (lowers > k) length = k; } } cout << longest << endl; return 0; } ``` <br /> 依照提示,依序讀取字串的每個字母,記錄字母的大、小寫狀態同時計算最長的交錯字串長度。程式碼的第29 ~ 59行共有4種狀況: 1. 第30 ~ 36行:這個字母是大寫、前一個字母也是大寫,先將連續大寫字母數量 uppers 加 1,連續小寫字母數量 lowers 歸零。接下來又分為兩種狀況: 1. uppers 等於 k,將目前的交錯字串長度 length 加 k,再用 max 更新最長的交錯字串長度 longest。 2. uppers 大於 k,代表這組連續大寫字母可能是下一組交錯字串的開頭,將 length 設定為 k。 2. 第37 ~ 43行:這個字母是小寫、前一個字母是大寫,主要分為3個步驟 1. 如果在這個字母之前的連續大寫字母數量 uppers 小於 k,代表這個字母可能是新的交錯字串開頭,先將 length 歸零。 2. 將 uppers 設定為 0、lowers 設定為 1、字母大寫狀態 capital 設定為 0。 3. 如果 k 等於 1,將 length 加 1,再用 max 更新最長的交錯字串長度 longest。 3. 第44 ~ 50行:這個字母是大寫、前一個字母是小寫,主要分為3個步驟 1. 如果在這個字母之前的連續小寫字母數量 lowers 小於 k,代表這個字母可能是新的交錯字串開頭,先將 length 歸零。 2. 將 uppers 設定為 1、lowers 設定為 0、字母大寫狀態 capital 設定為 1。 3. 如果 k 等於 1,將 length 加 1,再用 max 更新最長的交錯字串長度 longest。 4. 第51 ~ 58行:這個字母是小寫、前一個字母也是小寫,先將連續小寫字母數量 lowers 加 1,連續大寫字母數量 uppers 歸零。接下來又分為兩種狀況: 1. lowers 等於 k,將目前的交錯字串長度 length 加 k,再用 max 更新最長的交錯字串長度 longest。 2. lowers 大於 k,代表這組連續小寫字母可能是下一組交錯字串的開頭,將 length 設定為 k。 ZeroJudge 測試結果,花費時間為 4 ms,使用記憶體 388 kB,通過測試。 <br /><br /> ### 方法2:先找出連續大寫或小寫的字母數量 ```cpp= #include <iostream> #include <string> #include <deque> using namespace std; int main(int argc, char* argv[]) { // 由標準輸入讀取題目要求的交錯字串長度 k 以及字串 data int k; cin >> k; string data; cin >> data; // 將連續的大寫或小寫字母數量儲存到串列 data2 deque<int> data2; // 讀取開頭的字母 int uppers, lowers, capital; if (isupper(data[0])) { uppers = 1; lowers = 0; capital = 1; } else { uppers = 0; lowers = 1; capital = 0; } // 讀取索引值為1到最後一個字母,有4種狀況 for(int i=1; i<data.length(); i++) { if (isupper(data[i]) && capital == 1) { // 這個字母是大寫、前一個字母也是大寫 uppers++; lowers = 0; } else if (islower(data[i]) && capital == 1) { // 這個字母是小寫、前一個字母是大寫 data2.push_back(uppers); uppers = 0; lowers = 1; capital = 0; } else if (isupper(data[i]) && capital == 0) { // 這個字母是大寫、前一個字母是小寫 data2.push_back(lowers); uppers = 1; lowers = 0; capital = 1; } else if (islower(data[i]) && capital == 0) { // 這個字母是小寫、前一個字母也是小寫 uppers = 0; lowers++; } } // 加上最後一組連續大寫或小寫字母的長度 if (isupper(data[data.length()-1])) data2.push_back(uppers); else data2.push_back(lowers); // 讀取 data2 索引值為 0 的元素,若大於等於 k, // 則目前的交錯字串長度 length、最長的字串長度 longest 皆設定為k,反之設定為0 int length, longest; if (data2[0] >= k) { length = k; longest = k; } else { length = 0; longest = 0; } // 讀取 data2 索引值為 1 到最後一個元素,有5種狀況 for(int i=1; i<data2.size(); i++) { if (data2[i-1] == k && data2[i] == k) { // 前一組與這一組字串長度皆等於k length += k; longest = max(length, longest); } else if (data2[i-1] > k && data2[i] == k) { // 前一組字串長度大於k,這一組字串長度等於k length = 2*k; longest = max(length, longest); } else if (data2[i-1] < k && data2[i] == k) { // 前一組字串長度小於k,這一組字串長度等於k length = k; longest = max(length, longest); } else if (data2[i-1] == k && data2[i] > k) { // 前一組字串長度等於k,這一組字串長度大於k length += k; longest = max(length, longest); length = 0; } else { // 其它狀況,字串長度 length 歸零 length = 0; } } cout << longest << endl; return 0; } ``` <br /> 依照提示,先找出連續大寫或小寫的字母數量,記錄在 data2 之中,對應到程式碼的第14 ~ 41行,這部分的程式碼運作原則與方法1相同。接下來在第53 ~ 70行,計算交錯字串長度,有5種狀況: 1. 第54 ~ 56行:前一組與這一組字串長度皆等於 k,代表交錯字串長度還有可能會更長,先將 length 加 k,再用 max 更新最長的交錯字串長度 longest。 2. 第57 ~ 59行:前一組字串長度大於 k,這一組字串長度等於 k,代表前一組字串為交錯字串的開頭,先將 length 設定為 2\*k,再用 max 更新最長的交錯字串長度 longest。 3. 第60 ~ 62行:前一組字串長度小於 k,這一組字串長度等於 k,代表這一組字串為交錯字串的開頭,先將 length 設定為 k,再用 max 更新最長的交錯字串長度 longest。 4. 第63 ~ 66行:前一組字串長度等於 k,這一組字串長度大於 k,代表這一組字串為交錯字串的結尾,先將 length 加 k,再用 max 更新最長的交錯字串長度 longest,最後將 length 歸零。 5. 第67、68行:其它狀況,將 length 歸零。 由於這個方法比方法1多執行一個 for 迴圈,理論上執行時間較長。於 ZeroJudge 測試,花費時間為 4 ms,使用記憶體 384 kB,通過測試。 <br /><br /> ## 參考資料 參考資料:吳燦銘(2020)。**APCS大學程式設計先修檢測:C++超效解題致勝祕笈**。新北市:博碩文化。 8-7 <br /> --- ###### tags:`APCS`、`Python`