APCS Python 程式識讀|自訂題目試作 30 題
本版本將既有自訂題目與補強題合併,作為 APCS Python 程式識讀的試作題庫。
題目涵蓋程式追蹤、填空、測試除錯與演算法辨識,並加入 Python 串列參照、切片及可變物件等特性。
每題分為「1. 題目」與「2. 解析」;解析預設收合,作答後再點開。
1. 題目
下列程式執行後,輸出結果為何?
total = 0
for i in range(3):
for j in range(i, 4, 2):
for k in range(j, 5):
total += 1
print(total)
- 12
- 14
- 17
- 20
2. 解析(點擊後顯示)
最內層迴圈的執行次數取決於 j,因為 range(j, 5) 會產生 5-j 個整數。
| i | j 的值 | 各次 k 迴圈次數 | 小計 |
|---|---|---|---|
| 0 | 0、2 | 5、3 | 8 |
| 1 | 1、3 | 4、2 | 6 |
| 2 | 2 | 3 | 3 |
total = 8 + 6 + 3 = 17
答案:C,17。
1. 題目
下列程式執行後,輸出結果為何?
t = 0
def g(x, y):
global t
if x == 0 and y == 1:
t += 1
if x <= 0 or y <= 0:
return 3
return 5 + f(x, y - 1)
def f(x, y):
if x <= 0 or y <= 0:
return 2
return g(x - 1, y) + 2 * g(x - 2, y - 1)
print(f(5, 5), t)
229 8229 16226 8226 16
2. 解析(點擊後顯示)
先計算回傳值。對正整數參數而言:
g(x, y) = 5 + f(x, y - 1)
f(x, y) = g(x - 1, y) + 2 * g(x - 2, y - 1)
由較小狀態往上計算,可得:
f(2, 2) = 20
f(3, 3) = 53
f(4, 4) = 108
f(5, 5)
= g(4, 5) + 2 * g(3, 4)
= (5 + f(4, 4)) + 2 * (5 + f(3, 3))
= 113 + 2 * 58
= 229
t 只在實際呼叫到 g(0, 1) 時增加。注意 2 * g(...) 是把回傳值乘以 2,不是呼叫函式兩次。
到達 g(0, 1) 的次數:
1 → 2 → 3 → 5 → 8
因此 t=8。
答案:A,229 8。
1. 題目
下列函式使用「左右端點都包含」的區間進行二分搜尋。空格(甲)、(乙)、(丙)應填入哪一組,才能正確搜尋?
def binary_search(values, target):
left = 0
right = len(values) - 1
while (甲):
mid = (left + right) // 2
if values[mid] == target:
return mid
elif values[mid] < target:
left = (乙)
else:
right = (丙)
return -1
values = [1, 3, 4, 7, 8, 10, 12, 15]
print(binary_search(values, 10),
binary_search(values, 9))
left <= right、mid + 1、mid - 1left < right、mid + 1、mid - 1left <= right、mid、midleft < right、mid、mid
2. 解析(點擊後顯示)
目前的搜尋區間是閉區間 [left, right],左右端點都仍可能是答案,因此只要 left <= right 就要繼續搜尋。
若 values[mid] < target,索引 mid 已確定不是答案,下一個區間應從 mid + 1 開始;反方向同理,右端點應改為 mid - 1。
while left <= right:
...
left = mid + 1
...
right = mid - 1
完成後輸出為 5 -1。
答案:A。
1. 題目
下列程式執行後,輸出結果為何?
data = [95, 27, 90, 49, 80, 58, 6, 9, 18, 50]
count = 0
n = len(data)
for i in range(n - 1):
for j in range(n - i - 1):
if data[j] < data[j + 1]:
data[j], data[j + 1] = data[j + 1], data[j]
count += 1
print(count, data[0], data[-1])
14 95 631 95 614 6 9524 95 6
2. 解析(點擊後顯示)
條件 data[j] < data[j + 1] 會在左邊元素較小時交換,讓較大的數往左移,因此最後是由大到小排序。
各輪發生的交換次數依序為:
7、4、1、1、1、0、0、0、0
總交換次數為:
7 + 4 + 1 + 1 + 1 = 14
排序結果為:
[95, 90, 80, 58, 50, 49, 27, 18, 9, 6]
因此第一個元素是 95,最後一個元素是 6。
答案:A,14 95 6。
1. 題目
數字 1、2、3、4、5 只能依此順序放入 Stack(堆疊),但可在任意時機取出頂端元素。下列程式會判斷指定的取出順序是否可能發生。哪一個選項會使函式回傳 True?
def possible(order):
stack = []
next_value = 1
for x in order:
while next_value <= 5 and \
(not stack or stack[-1] != x):
stack.append(next_value)
next_value += 1
if stack and stack[-1] == x:
stack.pop()
else:
return False
return True
[3, 2, 5, 4, 1][3, 1, 2, 5, 4][2, 4, 1, 5, 3][4, 2, 5, 3, 1]
2. 解析(點擊後顯示)
Stack 採用 LIFO(後進先出):最後放入的元素必須最先取出。
選項 A 可依下列步驟完成:
push 1、2、3 → pop 3、2
push 4、5 → pop 5、4、1
實際取出順序正是:
3、2、5、4、1
其他選項都曾嘗試略過仍壓在目標元素上方的數字。例如選項 B 取出 3 後,堆疊頂端是 2,不可能直接取出位於下方的 1。
答案:A,[3, 2, 5, 4, 1]。
1. 題目
下列程式以列表搭配 front 模擬 Queue(佇列)。每次從佇列前端取出一個數字,轉換後再加入尾端。程式執行後輸出為何?
q = [2, 5, 1, 4]
front = 0
for _ in range(5):
x = q[front]
front += 1
if x % 2 == 0:
q.append(x // 2)
else:
q.append(x + 3)
print(q[front:])
[1, 8, 4, 2][8, 4, 2, 4][4, 2, 4, 8][2, 5, 1, 4]
2. 解析(點擊後顯示)
Queue 採用 FIFO(先進先出)。front 左側的資料雖然仍留在列表內,但已經視為離開佇列。
| 次數 | 取出 x | 加入尾端 | 處理後的有效佇列 |
|---|---|---|---|
| 1 | 2 | 1 | [5, 1, 4, 1] |
| 2 | 5 | 8 | [1, 4, 1, 8] |
| 3 | 1 | 4 | [4, 1, 8, 4] |
| 4 | 4 | 2 | [1, 8, 4, 2] |
| 5 | 1 | 4 | [8, 4, 2, 4] |
最後 front=5,所以切片 q[front:] 只保留尚未出隊的四個元素。
答案:B,[8, 4, 2, 4]。
1. 題目
下列程式使用 Stack(堆疊)計算 Postfix Expression(後序運算式)。其中除法使用整數除法。程式執行後輸出為何?
tokens = [9, 2, '-', 4, 3, '+', '*', 5, '/']
stack = []
for token in tokens:
if isinstance(token, int):
stack.append(token)
else:
b = stack.pop()
a = stack.pop()
if token == '+':
stack.append(a + b)
elif token == '-':
stack.append(a - b)
elif token == '*':
stack.append(a * b)
else:
stack.append(a // b)
print(stack[-1])
- 7
- 9
- 10
- 49
2. 解析(點擊後顯示)
遇到數字就推入堆疊;遇到運算子時,先取出的值是右運算元 b,後取出的值是左運算元 a。
| 讀到的片段 | 完成的運算 | 堆疊的重要結果 |
|---|---|---|
9 2 - | 9 - 2 | 7 |
4 3 + | 4 + 3 | 7 |
* | 7 × 7 | 49 |
5 / | 49 // 5 | 9 |
這串後序運算式等價於:
((9 - 2) * (4 + 3)) // 5
因為使用整數除法,所以 49 // 5 = 9。
答案:B,9。
1. 題目
下列函式使用 Binary Search(二分搜尋)尋找數字 3。四個數列都包含 3,但哪一個數列會使函式錯誤地回傳 -1?
def binary_search(values, target):
left = 0
right = len(values) - 1
while left <= right:
mid = (left + right) // 2
if values[mid] == target:
return mid
elif values[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
[1, 2, 3, 4, 5, 6][1, 4, 5, 2, 3, 6][2, 2, 3, 4, 4, 5][1, 1, 2, 3, 3, 3]
2. 解析(點擊後顯示)
這個版本的二分搜尋假設數列已按照「由小到大」排列。它根據中間值與目標值的大小關係,直接排除左半邊或右半邊。
選項 B 並未依照由小到大的順序排列。追蹤搜尋過程:
values = [1, 4, 5, 2, 3, 6]
left=0, right=5, mid=2
values[2]=5 > 3
→ right=1
left=0, right=1, mid=0
values[0]=1 < 3
→ left=1
left=1, right=1, mid=1
values[1]=4 > 3
→ right=0
搜尋區間因此變空,函式回傳 -1,但數字 3 實際位於索引 4。
二分搜尋每次都會根據中間值排除一半資料;只有資料已依照搜尋規則排序時,被排除的一半才保證不含目標值。本題因為資料順序混亂,第一次比較後就把真正含有 3 的右半邊排除了。
答案:B,[1, 4, 5, 2, 3, 6]。
1. 題目
下列 Bubble Sort(氣泡排序)不使用暫存變數,也不使用 Python 的多重指定,而是利用加減法交換相鄰元素。空格(甲)、(乙)應填入哪一組,才能正確完成由小到大排序?
def bubble_sort(data):
n = len(data)
for i in range(n - 1):
for j in range(n - i - 1):
if data[j] > data[j + 1]:
data[j] = data[j] + data[j + 1]
data[j + 1] = data[j] - (甲)
data[j] = data[j] - (乙)
data = [7, 2, 5, 1]
bubble_sort(data)
print(data)
(甲)data[j + 1] (乙)data[j + 1](甲)data[j] (乙)data[j + 1](甲)data[j + 1] (乙)data[j](甲)data[j] (乙)data[j]
2. 解析(點擊後顯示)
假設交換前:
data[j] = x
data[j + 1] = y
第一行先把兩數總和存到左邊:
data[j] = x + y
data[j + 1] = y
接著用總和減去右邊原本的 y,即可把右邊改成原本的 x:
data[j + 1] = (x + y) - y = x
最後再用總和減去目前右邊的 x,即可把左邊改成原本的 y:
data[j] = (x + y) - x = y
因此兩個空格都必須填入 data[j + 1]。排序完成後輸出:
[1, 2, 5, 7]
答案:A。
1. 題目
將下列 Infix Expression(中序運算式)轉換成 Postfix Expression(後序運算式),哪一個結果正確?
3 * 5 + 6 * 8 - 8 / 9
3 5 * 6 8 * + 8 9 / -3 5 6 8 * + * 8 9 / -3 5 * 6 8 * 8 9 / - +3 5 * 6 8 * + 8 9 - /
2. 解析(點擊後顯示)
乘法與除法的優先順序高於加法與減法,因此可以先看成:
(3 * 5) + (6 * 8) - (8 / 9)
三個乘除子運算式轉成後序後分別為:
3 5 *
6 8 *
8 9 /
原式的加法與減法具有相同優先順序,依由左到右結合,所以先把前兩段相加,再減去最後一段:
3 5 * 6 8 * + 8 9 / -
後序運算式不需要括號,因為運算子的出現位置已經完整表示運算順序。
答案:A,3 5 * 6 8 * + 8 9 / -。
1. 題目
下列程式執行後輸出為何?
a = [1, 2, 3]
b = a
b[1] = 9
b.append(4)
print(a)
[1, 2, 3][1, 9, 3][1, 2, 3, 4][1, 9, 3, 4]
2. 解析(點擊後顯示)
b = a 沒有建立新的串列;a 與 b 指向同一個 List(串列)物件。
因此透過 b 修改元素或加入資料,也會從 a 觀察到相同結果。
答案:D,[1, 9, 3, 4]。
1. 題目
下列程式執行後,a 與 b 分別為何?
a = [[1, 2], [3, 4]]
b = a[:]
b[0][0] = 9
b[1] = [7, 8]
print(a)
print(b)
[[1, 2], [3, 4]] [[9, 2], [7, 8]][[9, 2], [3, 4]] [[9, 2], [7, 8]][[9, 2], [7, 8]] [[9, 2], [7, 8]][[1, 2], [7, 8]] [[9, 2], [7, 8]]
2. 解析(點擊後顯示)
先用 L1~L5 為串列物件命名,方便追蹤參照關係:
L1 = [1, 2]
L2 = [3, 4]
L3 = [L1, L2]
a → L3
執行 b = a[:] 時,完整切片會建立一個新的外層串列 L4;但 L4 裡放入的仍是原本對 L1、L2 的參照:
L3 = [L1, L2]
L4 = [L1, L2]
a → L3
b → L4
所以 a、b 的外層串列不同,但 a[0] 與 b[0] 共同參照 L1,a[1] 與 b[1] 共同參照 L2。這就是 Shallow Copy(淺層複製)。
= 到底做了什麼?
= 本身不保證「建立新物件」。指定敘述會先計算右側運算式,取得某個物件,再把左側指定目標改成參照該物件。
x = y:通常只是讓x參照y已經參照的物件,不會自動複製。b = a[:]:是右側的切片運算建立新串列L4,再讓b參照L4。b[1] = [7, 8]:是右側的串列常值建立新串列L5,再把b[1]改成參照L5。
第一個操作:b[0][0] = 9
先沿著參照找到真正要修改的格子:
b → L4
b[0] → L1
b[0][0] → L1[0]
因此這行真正修改的是 L1[0]:把它原本指向整數 1 的參照,換成指向整數 9 的參照。
修改前:L1 = [1, 2]
修改後:L1 = [9, 2]
整數 1 本身沒有被改成 9;被改的是 L1[0] 這一格所保存的參照。因為 a[0]、b[0] 都指向同一個 L1,所以兩邊都會看到 [9, 2]。
第二個操作:b[1] = [7, 8]
右側 [7, 8] 會建立新的串列物件,暫時命名為 L5:
L5 = [7, 8]
左側指定目標是 b[1],也就是 L4[1]。因此程式把 L4[1] 原本指向 L2 的參照,改成指向 L5:
修改前:
L3 = [L1, L2]
L4 = [L1, L2]
修改後:
L3 = [L1, L2]
L4 = [L1, L5]
這個操作只改到 L4[1],沒有進入或修改 L2,也沒有修改 L3[1]。因此:
a[1] → L2 → [3, 4]
b[1] → L5 → [7, 8]
| 程式 | 真正被修改的格子 | 被替換的參照 | 原物件是否被修改 |
|---|---|---|---|
b[0][0] = 9 |
L1[0] |
1 → 9 |
整數 1 沒被修改 |
b[1] = [7, 8] |
L4[1] |
L2 → L5 |
L2 沒被修改 |
最後的參照關係為:
L1 = [9, 2]
L2 = [3, 4]
L5 = [7, 8]
a → L3 = [L1, L2]
b → L4 = [L1, L5]
所以輸出為:
[[9, 2], [3, 4]]
[[9, 2], [7, 8]]
答案:B。
1. 題目
下列程式原本想建立三個互相獨立的列。執行後實際輸出為何?
grid = [[0] * 3] * 3
grid[1][2] = 7
print(grid)
[[0, 0, 0], [0, 0, 7], [0, 0, 0]][[0, 0, 7], [0, 0, 7], [0, 0, 7]][[7, 0, 0], [7, 0, 0], [7, 0, 0]]- 程式會在建立
grid時發生錯誤
2. 解析(點擊後顯示)
我們用紙筆標籤來表示物件:
K:代表整數物件0M:代表整數物件7L1:代表內層串列物件L2:代表最外層的grid串列物件
K、M 是為了畫圖方便所使用的標籤;實際 Python 程式中沒有建立名為 K、M 的變數。
第一步:計算 [0] * 3
右側先建立一個只含 0 的串列,再把其中的元素參照重複三次。概念上可以寫成:
K = 整數物件 0
L1 = [K, K, K]
也就是:
L1[0] → K
L1[1] → K
L1[2] → K
L1 的三個格子都參照整數物件 0。整數是 Immutable Object(不可變物件),但這不是本題發生三列連動的主要原因。
第二步:計算 [L1] * 3
外層的 * 3 並不會建立三個獨立的內層串列,而是把同一個 L1 的參照放入外層三次:
L2 = [L1, L1, L1]
grid → L2
因此:
grid[0] → L1
grid[1] → L1
grid[2] → L1
這三個位置看到的是同一個可變串列物件 L1。
第三步:執行 grid[1][2] = 7
先追蹤左側到底指向哪一個格子:
grid → L2
grid[1] → L1
grid[1][2]→ L1[2]
右側的 7 會求值得到整數物件 7,我們用紙筆標籤 M 表示它。
所以這行真正做的是:
原本:L1[2] → K
後來:L1[2] → M
也就是把 L1 索引 2 這一格所保存的參照,從整數物件 0 改成整數物件 7:
修改前:L1 = [K, K, K]
修改後:L1 = [K, K, M]
這裡沒有把 K 物件本身改成 M,也沒有改變其他仍指向 K 的格子;真正被修改的是 L1[2] 這個串列位置。
為什麼三列都出現 7?
因為外層三個位置全部指向同一個 L1:
grid[0] → L1
grid[1] → L1
grid[2] → L1
而 L1 現在已經是:
[K, K, M] = [0, 0, 7]
所以從 grid[0]、grid[1] 或 grid[2] 查看,都會看到同一份內容:
[[0, 0, 7], [0, 0, 7], [0, 0, 7]]
| 操作 | 真正被修改的位置 | 參照變化 | 沒有被修改的東西 |
|---|---|---|---|
grid[1][2] = 7 |
L1[2] |
K → M |
K 代表的整數物件 0、以及 L1[0]、L1[1] |
如何建立三個互相獨立的列?
要在每一輪都重新建立一個新的內層串列:
grid = [[0] * 3 for _ in range(3)]
概念上會得到三個不同物件:
L1 = [K, K, K]
L2 = [K, K, K]
L3 = [K, K, K]
grid = [L1, L2, L3]
此時修改其中一列,不會連動其他列。
答案:B。
1. 題目
下列程式執行後輸出為何?
def change(x, data):
x = x + 5
data[0] = data[0] + 5
data = data + [99]
n = 10
a = [10]
change(n, a)
print(n, a)
10 [10]15 [15, 99]10 [15]15 [15]
2. 解析(點擊後顯示)
這題要分清楚兩種操作:
- 修改物件內容:例如
data[0] = ...,被修改的是串列物件裡的某一格,串列本身仍是原本同一個物件。 - 重新指定名稱:例如
data = ...,被改變的是名稱data的參照;原本的物件不會因此在所有地方一起被替換。
先替物件加上紙筆標籤
為了追蹤參照關係,我們使用下列標籤:
K:整數物件10M:整數物件15N:整數物件99L1:原本的串列物件[10]L2:右側串列常值所建立的串列物件[99]L3:串接後產生的新串列物件[15, 99]
這些字母只是紙筆分析用的標籤;原程式中沒有真的宣告 K、M、N、L1、L2、L3。
第一步:建立外部變數
n = 10
a = [10]
概念上可表示為:
K = 整數物件 10
L1 = [K]
n → K
a → L1
第二步:呼叫 change(n, a)
呼叫函式時,不會自動複製 10 或串列 [10]。函式參數名稱會參照呼叫端傳入的物件:
外部:
n → K
a → L1
函式內:
x → K
data → L1
因此一開始:
n、x都參照整數物件K。a、data都參照同一個串列物件L1。
第三步:執行 x = x + 5
先計算右側:
x + 5
10 + 5
→ 15
右側運算得到整數物件 15,用標籤 M 表示。接著 = 只讓函式內的名稱 x 改為參照 M:
執行前:
n → K
x → K
執行後:
n → K
x → M
真正被修改的是名稱 x 的參照;整數物件 K 沒有被改成 M,外部名稱 n 也沒有被重新指定,所以 n 仍是 10。
第四步:執行 data[0] = data[0] + 5
先追蹤左側位置:
data → L1
data[0] → L1[0]
右側 data[0] + 5 得到整數物件 15,也就是 M。因此真正發生的是:
原本:L1[0] → K
後來:L1[0] → M
修改後:
L1 = [M] = [15]
這裡修改的是原串列物件 L1 的內容。L1 仍然是原本同一個串列物件,只是索引 0 保存的參照由 K 換成 M。
因為外部的 a 和函式內的 data 都參照 L1:
a → L1
data → L1
所以外部的 a 也會看到 L1 已變成 [15]。
第五步:為何 data = data + [99] 會產生新串列?
這一行必須分成「計算右側」與「指定左側」兩部分:
5.1 先建立右側的 [99]
串列常值 [99] 會建立新的串列物件,假設叫做 L2:
N = 整數物件 99
L2 = [N] = [99]
5.2 再計算 data + [99]
此時:
data → L1
L1 = [M] = [15]
L2 = [N] = [99]
串列的 + 是 Concatenation(串接)運算。它會建立一個新的串列,把左右兩個串列中的元素參照依序放入新串列,而不會直接修改原本的 L1 或 L2。
因此:
L1 + L2
→ 建立 L3
L3 = [M, N] = [15, 99]
運算完成後,三個串列物件同時存在:
L1 = [15]
L2 = [99]
L3 = [15, 99]
5.3 最後執行 data = L3
右側完成後,= 才讓函式內的名稱 data 改為參照新串列 L3:
執行前:
a → L1
data → L1
執行後:
a → L1
data → L3
所以這裡不是把原本的 L1 在所有地方「整個換掉」,而只是重新指定函式內的區域名稱 data。
外部名稱 a 沒有被重新指定,仍然參照原本的 L1:
a → L1 = [15]
data → L3 = [15, 99]
函式結束後,區域名稱 x、data 消失;外部仍然是:
n → K = 10
a → L1 = [15]
= 本身不一定建立新物件
本題中,新串列不是由 = 自己建立,而是由右側運算式建立:
[99]建立新的串列L2。data + [99]的串接運算建立新的串列L3。=最後只把左側名稱data改為參照L3。
例如 x = y 通常不會建立副本,只會讓 x 也參照 y 當時所參照的物件。
三個指定敘述對照
| 程式 | 真正被修改的目標 | 參照變化 | 外部是否受影響 |
|---|---|---|---|
x = x + 5 |
區域名稱 x |
x:K → M |
n 不受影響 |
data[0] = data[0] + 5 |
原串列 L1[0] |
L1[0]:K → M |
a 會看到 L1 的修改 |
data = data + [99] |
區域名稱 data |
data:L1 → L3 |
a 仍參照 L1 |
與 append() 的差別
若最後一行改成:
data.append(99)
它會直接修改原本的 L1:
L1:[15] → [15, 99]
因為 a 也參照 L1,此時外部的 a 就會變成 [15, 99]。但原題使用的是 data = data + [99],所以建立新串列後只重新指定區域名稱 data。
最後結果
n = 10
a = [15]
因此:
print(n, a)
→ 10 [15]
答案:C,10 [15]。
1. 題目
下列程式執行後輸出為何?
a = [3, 1, 2]
b = a.sort()
c = sorted(a, reverse=True)
print(a)
print(b)
print(c)
[1, 2, 3] None [3, 2, 1][3, 1, 2] [1, 2, 3] [3, 2, 1][1, 2, 3] [1, 2, 3] [3, 2, 1][3, 2, 1] None [1, 2, 3]
2. 解析(點擊後顯示)
list.sort() 直接修改原串列,並回傳 None;sorted() 則建立並回傳新的排序結果。
因此 a 先變成遞增排序,b 是 None,而 c 是依目前 a 建立的遞減排序新串列。
答案:A。
1. 題目
下列程式執行後輸出為何?
s = "ABCDEFG"
print(s[-2], s[1:6:2], s[::-2])
F BDF GECAF BDF ACEGG BCE GECF BDF GFEDCBA
2. 解析(點擊後顯示)
s[-2] 是倒數第二個字元 F;s[1:6:2] 取索引 1、3、5,得到 BDF。
s[::-2] 從最後一個字元開始,每次向左兩格,索引依序為 6、4、2、0,得到 GECA。
答案:A。
1. 題目
下列程式執行後輸出為何?
def check(s):
left = 0
right = len(s) - 1
count = 0
while left < right:
count += 1
if s[left] != s[right]:
return False, count
left += 1
right -= 1
return True, count
print(check("LEVEL"), check("LEVER"))
(True, 2) (False, 1)(True, 3) (False, 2)(False, 2) (True, 1)(True, 5) (False, 5)
2. 解析(點擊後顯示)
LEVEL 只需比較第 0 與 4、以及第 1 與 3 個字元,中間字元不需與自己比較,因此共 2 次。
LEVER 的第一組字元 L、R 就不同,第一次比較後立即回傳。
答案:A。
1. 題目
下列程式要將 AAABBCCCC 壓縮成 A3B2C4。空格應填入哪一行?
s = "AAABBCCCC"
result = ""
count = 1
for i in range(1, len(s)):
if s[i] == s[i - 1]:
count += 1
else:
result += s[i - 1] + str(count)
count = 1
____________________________
print(result)
result += s[0] + str(count)result += s[-1] + str(count)result += s[i - 1] + str(i)result += str(count) + s[0]
2. 解析(點擊後顯示)
迴圈只在字元發生變化時輸出前一組,因此最後一組 CCCC 不會在迴圈內被加入。
迴圈結束後,最後字元是 s[-1],而 count 保存最後一組的數量。
答案:B,result += s[-1] + str(count)。
1. 題目
下列 Selection Sort 執行完第一次外層迴圈,也就是 i == 0 的那一輪後,a 為何,且實際交換幾次?
a = [7, 3, 5, 2]
for i in range(len(a) - 1):
pos = i
for j in range(i + 1, len(a)):
if a[j] < a[pos]:
pos = j
a[i], a[pos] = a[pos], a[i]
[3, 7, 5, 2][2, 3, 5, 7],且第一輪發生兩次交換[2, 3, 5, 7],且第一輪發生三次交換[2, 3, 5, 7],且第一輪只在最後交換一次
2. 解析(點擊後顯示)
第一輪會掃描索引 1~3,記住最小值 2 的位置,直到內層迴圈結束後才與 a[0] 交換。
因此第一輪結束後是 [2, 3, 5, 7],而不是每次發現較小值就交換。
答案:D。
1. 題目
下列程式將索引 3 的元素插入前方已排序區域。執行後 a 為何?
a = [2, 5, 7, 3, 9]
key = a[3]
j = 2
while j >= 0 and a[j] > key:
a[j + 1] = a[j]
j -= 1
a[j + 1] = key
[2, 5, 3, 7, 9][2, 3, 5, 7, 9][3, 2, 5, 7, 9][2, 5, 7, 9, 3]
2. 解析(點擊後顯示)
key=3。7 與 5 都比 3 大,因此依序向右移動;2 不大於 3,迴圈停止,最後將 3 放到索引 1。
答案:B,[2, 3, 5, 7, 9]。
1. 題目
下列程式的主要特徵是選出一個 key,把較大的元素向右搬移,再把 key 放入空出的正確位置。這是哪一種演算法?
for i in range(1, len(a)):
key = a[i]
j = i - 1
while j >= 0 and a[j] > key:
a[j + 1] = a[j]
j -= 1
a[j + 1] = key
- Bubble Sort(氣泡排序)
- Selection Sort(選擇排序)
- Insertion Sort(插入排序)
- Binary Search(二分搜尋)
2. 解析(點擊後顯示)
程式維持左側的已排序區域,每輪把新元素插入其中,這是 Insertion Sort。
Bubble Sort 以相鄰比較交換為主;Selection Sort 每輪尋找剩餘資料中的最小值。
答案:C。
1. 題目
下列程式執行後輸出為何?
a = [4, 8, 1, 6, 9, 3]
target = 9
comparisons = 0
for i in range(len(a)):
comparisons += 1
if a[i] == target:
break
print(i, comparisons)
4 44 55 55 6
2. 解析(點擊後顯示)
目標 9 位於索引 4。程式會比較索引 0、1、2、3、4,共 5 次,找到後停止。
答案:B,4 5。
1. 題目
下列函式想回傳串列中的最大值,但初始化方式有問題。哪一組輸入最能測出錯誤?
def find_max(a):
answer = 0
for x in a:
if x > answer:
answer = x
return answer
[1, 2, 3][0, 5, 2][-5, -2, -8][10]
2. 解析(點擊後顯示)
所有元素都是負數時,沒有任何元素大於初始值 0,函式會錯誤回傳 0,而實際最大值應為 -2。
較可靠的初始化方式是使用 answer = a[0],再從後續元素開始比較。
答案:C。
1. 題目
下列函式要回傳已排序串列中 target 第一次出現的索引。找到目標後,空格應填什麼?
def first_pos(values, target):
left = 0
right = len(values) - 1
answer = -1
while left <= right:
mid = (left + right) // 2
if values[mid] == target:
answer = mid
__________________
elif values[mid] < target:
left = mid + 1
else:
right = mid - 1
return answer
left = mid + 1right = mid - 1return midright = mid + 1
2. 解析(點擊後顯示)
找到一個目標後,先記錄索引,但還要繼續檢查左半部是否有更早出現的相同值,因此應將右界移到 mid - 1。
答案:B。
1. 題目
下列程式以固定長度串列模擬 Stack。關於這組函式,下列敘述何者正確?
storage = [None] * 3
top = 0
def push(x):
global top
storage[top] = x
top += 1
def pop():
global top
top -= 1
return storage[top]
- 空堆疊呼叫
pop()時會回傳None,且top仍維持 0 - 空堆疊呼叫
pop()時,top會變成 -1,並讀取串列最後一格,未正確偵測下溢 - 堆疊滿時呼叫
push(),Python 會自動擴充storage top永遠指向目前堆疊頂端的元素
2. 解析(點擊後顯示)
這裡的 top 指向「下一個可寫入位置」。空堆疊時為 0,若直接 pop(),會先減成 -1。
Python 的負索引 -1 會讀取最後一格,所以程式不一定立即報錯,反而可能回傳無效資料,這是更隱蔽的 Stack Underflow 問題。
答案:B。
1. 題目
下列程式以固定長度串列模擬 Queue。依序加入 10、20、30,取出兩次,再加入 40、50。此時若再加入 60,主要問題是什麼?
Q = [None] * 5
front = 0
rear = 0
def enqueue(x):
global rear
Q[rear] = x
rear += 1
def dequeue():
global front
x = Q[front]
front += 1
return x
- 60 會自動存到索引 0,因此沒有問題
- 雖然索引 0、1 已空出,但
rear已到 5,再寫入會越界;函式沒有重用前端空間 dequeue()會從尾端取出資料- Queue 中仍有五個有效元素,因此本來就不應再加入
2. 解析(點擊後顯示)
取出兩次後,索引 0、1 已不再保存有效佇列資料,但 rear 只會持續增加,不會回到前方空位。
加入 40、50 後 rear=5,下一次執行 Q[5] = 60 會越界。可用 Circular Queue(循環佇列)或搬移有效資料解決。
答案:B。
1. 題目
下列程式執行後輸出為何?
a = [1, 2, 2, 2, 3]
for x in a:
if x == 2:
a.remove(x)
print(a)
[1, 3][1, 2, 3][1, 2, 2, 3]- 迴圈會無限執行
2. 解析(點擊後顯示)
迴圈走訪串列時刪除元素,後方元素會向左移,但迭代位置仍繼續前進,因此某些元素會被跳過。
本題第一次刪除後,下一個 2 移到目前索引卻未被檢查;最後仍留下其中一個 2。
答案:B,[1, 2, 3]。
1. 題目
下列程式執行後輸出為何?
calls = 0
def check(x):
global calls
calls += 1
return x > 0
result = check(0) and check(1) or check(2)
print(result, calls)
False 1False 2True 2True 3
2. 解析(點擊後顯示)
and 的優先順序高於 or,因此可先理解成:
(check(0) and check(1)) or check(2)
第一次呼叫:
check(0)
calls = 1
回傳 False
因為 False and ... 的結果已確定為 False,所以 check(1) 不會執行。
接著運算:
False or check(2)
or 左側為 False,因此必須計算右側:
check(2)
calls = 2
回傳 True
| 運算 | 是否執行 | 回傳值 | calls |
|---|---|---|---|
check(0) | 是 | False | 1 |
check(1) | 否,因 and 短路 | — | 1 |
check(2) | 是 | True | 2 |
所以輸出為:
True 2
答案:C。
1. 題目
下列程式執行後輸出為何?
grid = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
total = 0
for i in range(len(grid)):
for j in range(len(grid[i])):
if i == j or i + j == len(grid) - 1:
total += grid[i][j]
print(total)
20253035
2. 解析(點擊後顯示)
grid 是一個 3×3 的二維串列:
索引 j=0 j=1 j=2
i=0 1 2 3
i=1 4 5 6
i=2 7 8 9
i == j 代表主對角線:
(0, 0) → 1
(1, 1) → 5
(2, 2) → 9
i + j == len(grid) - 1 代表副對角線。因為 len(grid) - 1 = 2:
(0, 2) → 3
(1, 1) → 5
(2, 0) → 7
中心位置 (1, 1) 同時符合兩個條件,但同一回合的 if 只會執行一次,因此 5 只加一次。
1 + 3 + 5 + 7 + 9 = 25
| 座標 | 值 | 符合條件 |
|---|---|---|
(0, 0) | 1 | 主對角線 |
(0, 2) | 3 | 副對角線 |
(1, 1) | 5 | 兩者皆符合,但只加一次 |
(2, 0) | 7 | 副對角線 |
(2, 2) | 9 | 主對角線 |
所以輸出為:
25
答案:B。
1. 題目
下列程式執行後輸出為何?
a = 48
b = 18
count = 0
while b != 0:
a, b = b, a % b
count += 1
print(a, count)
6 26 312 212 3
2. 解析(點擊後顯示)
這段程式使用 Euclidean Algorithm(輾轉相除法)計算最大公因數。
這一行會先使用更新前的 a、b 計算右側,再同時指定給左側:
a, b = b, a % b
| 回合 | 原本 a | 原本 b | a % b | 更新後 (a, b) | count |
|---|---|---|---|---|---|
| 1 | 48 | 18 | 12 | (18, 12) | 1 |
| 2 | 18 | 12 | 6 | (12, 6) | 2 |
| 3 | 12 | 6 | 0 | (6, 0) | 3 |
第三回合後 b = 0,所以 b != 0 不成立,迴圈結束。
a = 6
count = 3
所以輸出為:
6 3
答案:B。