# APCS實作題2025年1月第2題:字串操作
> 日期:2025年1月15日
> 作者:王一哲
> [ZeroJudge 題目連結](https://zerojudge.tw/ShowProblem?problemid=q182)
## 題目
### 問題描述
給一個字串 $S$,有三種字串操作,分別如下
1. **編號0**,兩兩交換:將字串內相鄰兩個字元交換,例如字串 "apcsntnu" 先分組成 (ap)(cs)(nt)(nu),將會交換為 (pa)(sc)(tn)(un),即得到 "pasctnun"。
2. **編號1**,兩兩排序:將字串內相鄰兩個字元按照字典序排序,字元的字典序為 $a < b < \dots < z$,例如字串 "family" 先分組成 (fa)(mi)(ly),將會交換成 (af)(im)(ly),即得到 "afimly"。
3. **編號2**,完美重排:假設字串長度為 $n$,將字串內的字元編號為 $0, 1, 2, \dots, n-1$,將字串分成兩半為 $0, 1, 2, \dots, \frac{n}{2} - 1$ 和 $\frac{n}{2}, \frac{n}{2} + 1, \frac{n}{2} + 2, \dots, n-1$,並且組合成 $0, \frac{n}{2}, 1, \frac{n}{2} + 1, \dots, \frac{n}{2} - 1, \frac{n}{2} + 1$。例如 "apcsntnu" 拆成 "apcs" 和 "ntnu",然後交錯成 "anptcnsu"。
給定 $k$ 個操作,請依序操作字串,輸出最後的字串結果。
<br />
### 輸入說明
第一行輸入一個字串 $S~(2 \leq |S| \leq 100)$,字串長度保證為偶數。接下來一行輸入一個正整數 $k$,接下來有 $k$ 行,每一行有一個數字,種類為 $0, 1, 2$。
子題配分
- 60%:$|S| \leq 10, k = 1$ 且保證操作為完美重排
- 40%:無限制
<br />
### 輸出格式
輸出操作完之後的字串內容。
<br />
### 範例輸入1
```
apcsntnu
1
2
```
### 範例輸出1
```
anptcnsu
```
### 範例輸入2
```
facebook
4
2
0
2
1
```
### 範例輸出2
```
bocfkoae
```
<br /><br />
## Python 程式碼
### 寫法1,利用字串的索引值,不自訂函式
費時最久約為 23 ms,使用記憶體最多約為 3.3 MB,通過測試。
```python=
S = input().strip() # 輸入的字串
n = len(S) # 字串長度
k = int(input()) # 操作次數
for _ in range(k): # 執行 k 次
op = int(input()) # 操作種類
t = "" # 儲存操作結果用的空字串
if op == 0: # 操作0,兩兩交換
for i in range(0, n, 2): # i 每次加 2
t += S[i+1] + S[i]
elif op == 1: # 操作1,兩兩排序
for i in range(0, n, 2): # i 每次加 2
a, b = S[i], S[i+1] # 取出兩個字元 a, b
if a <= b: t += a+b # 如果 a <= b,將 a+b 接到 t 最後面
else: t += b+a # 反之,將 b+a 接到 t 最後面
else: # 操作2,完美重排
for i in range(n//2): # 執行 n//2 次
t += S[i] + S[n//2 + i]
S = t # t 字串存到 S
print(S)
```
<br />
### 寫法2,利用字串的索引值,自訂函式
費時最久約為 20 ms,使用記憶體最多約為 3.4 MB,通過測試。
```python=
# 寫法2,利用字串的索引值,自訂函式
def zero(s): # 操作0,兩兩交換
t = "" # 儲存操作結果用的空字串
for i in range(0, len(s), 2): # i 每次加 2
t += s[i+1] + s[i]
return t
def one(s): # 操作1,兩兩排序
t = "" # 儲存操作結果用的空字串
for i in range(0, len(s), 2): # i 每次加 2
a, b = S[i], S[i+1] # 取出兩個字元 a, b
if a <= b: t += a+b # 如果 a <= b,將 a+b 接到 t 最後面
else: t += b+a # 反之,將 b+a 接到 t 最後面
return t
def two(s): # 操作2,完美重排
t = "" # 儲存操作結果用的空字串
n = len(s) # 字串長度
for i in range(n//2): # 執行 n//2 次
t += S[i] + S[n//2 + i]
return t
S = input().strip() # 輸入的字串
k = int(input()) # 操作次數
for _ in range(k): # 執行 k 次
op = int(input()) # 操作種類
if op == 0: S = zero(S)
elif op == 1: S = one(S)
else: S = two(S)
print(S)
```
<br />
### 寫法3,利用字串切片 (slice) 及 zip
Python 的字串及串列的切片,語法為
```python
S[起點:終點:間隔]
```
取出的資料包含起點,起點預設值為開頭;不包含終點,終點預設值為結尾;間隔預設值為1,如果間隔為2每次跳兩格,如果間隔為-1會倒過來取值。如果想要將一個字串反向輸出,可以寫成
```python
S[::-1]
```
費時最久約為 22 ms,使用記憶體最多約為 3.3 MB,通過測試。
```python=
# 寫法3,利用字串切片 (slice) 及 zip
S = input().strip() # 輸入的字串
n = len(S) # 字串長度
for _ in range(int(input())): # 執行 k 次
op = int(input()) # 操作種類
t = "" # 儲存操作結果用的空字串
if op == 0: # 操作0,兩兩交換
for a, b in zip(S[::2], S[1::2]):
t += b+a
elif op == 1: # 操作1,兩兩排序
for a, b in zip(S[::2], S[1::2]):
t += a+b if a <= b else b+a
else: # 操作2,完美重排
for a, b in zip(S[:n//2], S[n//2:]):
t += a+b
S = t # t 字串存到 S
print(S)
```
<br />
### 寫法4,先將字串轉成串列 (list),再利用切片 (slice) 修改串列內容
這是我看到最有 Python 風格的寫法,但是對於初學者最難看懂。由於 Python 的字串不能透過索引值修改某個字元的內容,所以要先將字串轉成串列才行。費時最久約為 27 ms,使用記憶體最多約為 3.3 MB,通過測試。
```python=
S = list(input()) # 輸入的字串轉成串列
n = len(S) # 串列長度
for _ in range(int(input())): # 執行 k 次
op = int(input()) # 操作種類
if op == 0: # 操作0,兩兩交換
S[::2], S[1::2] = S[1::2], S[::2]
elif op == 1: # 操作1,兩兩排序
for i in range(0, n, 2): # 執行 n//2 次
S[i:i+2] = sorted(S[i:i+2])
else: # 操作2,完美重排
S[::2], S[1::2] = S[:n//2], S[n//2:]
print("".join(S)) # 將 S 接成字串再輸出
```
<br /><br />
## C++ 程式碼
### 寫法1,利用字串的索引值,不自訂函式
費時最久約為 3 ms,使用記憶體最多約為 352 kB,通過測試。
```cpp=
#include <iostream>
#include <string>
using namespace std;
int main() {
ios::sync_with_stdio(0); cin.tie(0);
string S; cin >> S; // 輸入的字串
int n = (int)S.size(); // 字串長度
int k; cin >> k; // 操作次數
for(int j=0, op; j<k; j++) { // 執行 k 次
cin >> op; // 操作種類
if (op == 0) { // 操作0,兩兩交換
for(int i=0; i<n; i+=2) { // i 每次加 2
char a = S[i]; // 暫存 S[i]
S[i] = S[i+1]; // S[i] 重設為 S[i+1]
S[i+1] = a; // S[i+1] 重設為 a
}
} else if (op == 1) { // 操作1,兩兩排序
for(int i=0; i<n; i+= 2) { // i 每次加 2
char a = S[i], b = S[i+1]; // 取出兩個字元 a, b
if (a > b) { // 如果 a > b
S[i] = b; S[i+1] = a; // S[i] 重設為 b,S[i+1] 重設為 a
}
}
} else { // 操作2,完美重排
string t = S; // 字串 S 暫存到 t
for(int i=0; i<n/2; i++) { // 執行 n/2 次
S[2*i] = t[i]; S[2*i+1] = t[n/2+i]; // S[2*i] 重設為 t[i],S[2*i+1] 重設為 t[n/2+1]
}
}
}
cout << S << "\n";
return 0;
}
```
<br />
### 寫法2,利用字串的索引值,自訂函式
費時最久約為 3 ms,使用記憶體最多約為 344 kB,通過測試。
```cpp=
#include <iostream>
#include <string>
using namespace std;
string zero(string s) { // 操作0,兩兩交換
string t = ""; // 暫存輸出結果的空字串
for(int i=0; i<(int)s.size(); i+=2) t = t + s[i+1] + s[i];
return t;
}
string one(string s) { // 操作1,兩兩排序
string t = ""; // 暫存輸出結果的空字串
for(int i=0; i<(int)s.size(); i+=2) {
if (s[i] <= s[i+1]) t = t + s[i] + s[i+1];
else t = t + s[i+1] + s[i];
}
return t;
}
string two(string s) { // 操作2,完美重排
string t = ""; // 暫存輸出結果的空字串
int n = (int)s.size(); // 字串長度
for(int i=0; i<n/2; i++) t = t + s[i] + s[n/2+i];
return t;
}
int main() {
ios::sync_with_stdio(0); cin.tie(0);
string S; cin >> S; // 輸入的字串
int k; cin >> k; // 操作次數
for(int j=0, op; j<k; j++) { // 執行 k 次
cin >> op; // 操作種類
if (op == 0) S = zero(S);
else if (op == 1) S = one(S);
else S = two(S);
}
cout << S << "\n";
return 0;
}
```
<br /><br />
---
###### tags:`APCS`、`Python`、`C++`