A3: 數據的容器:將 List 作為一維陣列
單元目標
- 掌握 List 的創建、存取與修改
- 理解常用操作的時間複雜度
- 學會遍歷與處理列表資料
- 建立效能意識,選擇適當的資料結構
為什麼要學 List?
處理單一數據點遠遠不夠,程式設計的威力體現在處理成批的、結構化的資料。
Python 的 list(列表)是最常用、最靈活的序列容器。在 APCS 基礎階段,我們將其視為**一維陣列(Array)**來使用,這是處理批量資料的基礎工具。
從統計一組成績,到記錄一系列感測器數據,list 無處不在。掌握 list 的操作,是邁向 APCS 2 級分的關鍵。
概念基礎
序列(Sequence)的概念
序列是一種資料結構,用於儲存多個有序的元素。Python 中的序列包括:
- list(列表):可變序列
- tuple(元組):不可變序列
- str(字串):不可變字元序列
零基索引(Zero-based Indexing)
重要概念
在程式設計中,索引從 0 開始,而非 1!
- 第 1 個元素的索引是 0
- 第 2 個元素的索引是 1
- 第 n 個元素的索引是 n-1
numbers = [10, 20, 30, 40, 50]
# ↑ ↑ ↑ ↑ ↑
# 索引: 0 1 2 3 4Python 實踐
1. 創建 List
基本創建方式
# 空列表
empty_list = []
# 直接初始化
numbers = [1, 2, 3, 4, 5]
fruits = ["蘋果", "香蕉", "橘子"]
mixed = [1, "hello", 3.14, True] # 可以混合不同型別
# 使用 list() 轉換
chars = list("Python") # ['P', 'y', 't', 'h', 'o', 'n']創建特定大小的列表
# 創建包含 n 個 0 的列表
n = 5
zeros = [0] * n # [0, 0, 0, 0, 0]
# 創建包含 n 個相同元素
n = 3
items = ["x"] * n # ['x', 'x', 'x']淺拷貝陷阱
# ❌ 錯誤:創建包含列表的列表
matrix = [[0] * 3] * 2
matrix[0][0] = 1
print(matrix) # [[1, 0, 0], [1, 0, 0]] <- 兩行都被修改了!
# ✅ 正確:使用列表生成式
matrix = [[0] * 3 for _ in range(2)]
matrix[0][0] = 1
print(matrix) # [[1, 0, 0], [0, 0, 0]] <- 只有第一行被修改列表生成式(List Comprehension)
強大且簡潔的創建方式:
# 基本語法
squares = [x**2 for x in range(5)]
# [0, 1, 4, 9, 16]
# 帶條件篩選
evens = [x for x in range(10) if x % 2 == 0]
# [0, 2, 4, 6, 8]
# 應用函式
words = ["hello", "world"]
upper_words = [w.upper() for w in words]
# ['HELLO', 'WORLD']
# 二維列表
matrix = [[i+j for j in range(3)] for i in range(3)]
# [[0, 1, 2], [1, 2, 3], [2, 3, 4]]2. 存取與修改
基本索引
numbers = [10, 20, 30, 40, 50]
# 正向索引(從 0 開始)
print(numbers[0]) # 10
print(numbers[2]) # 30
print(numbers[4]) # 50
# 負向索引(從 -1 開始,表示倒數)
print(numbers[-1]) # 50(最後一個)
print(numbers[-2]) # 40(倒數第二個)
# 修改元素
numbers[0] = 100
print(numbers) # [100, 20, 30, 40, 50]切片(Slicing)
切片是 Python 的強大功能,語法:list[start:stop:step]
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# 基本切片
print(numbers[2:5]) # [2, 3, 4](索引 2 到 4)
print(numbers[:3]) # [0, 1, 2](開頭到索引 2)
print(numbers[7:]) # [7, 8, 9](索引 7 到結尾)
print(numbers[:]) # [0, 1, ..., 9](複製整個列表)
# 步長切片
print(numbers[::2]) # [0, 2, 4, 6, 8](每隔一個)
print(numbers[1::2]) # [1, 3, 5, 7, 9](奇數索引)
print(numbers[::-1]) # [9, 8, 7, ..., 0](反轉列表)
# 負索引切片
print(numbers[-3:]) # [7, 8, 9](最後三個)
print(numbers[:-2]) # [0, 1, ..., 7](除了最後兩個)3. 遍歷(Traversal)
基於值的遍歷
fruits = ["蘋果", "香蕉", "橘子"]
# 直接遍歷元素
for fruit in fruits:
print(fruit)基於索引的遍歷
fruits = ["蘋果", "香蕉", "橘子"]
# 使用 range 和 len
for i in range(len(fruits)):
print(f"索引 {i}: {fruits[i]}")同時取得索引和值
fruits = ["蘋果", "香蕉", "橘子"]
# 使用 enumerate(推薦)
for index, fruit in enumerate(fruits):
print(f"{index}: {fruit}")
# 從 1 開始編號
for index, fruit in enumerate(fruits, start=1):
print(f"第 {index} 個: {fruit}")選擇建議
- 需要索引:使用
enumerate()或range(len()) - 只需要值:直接
for item in list - 需要修改元素:使用索引方式
4. 常用操作
查詢資訊
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
# 長度
print(len(numbers)) # 8
# 統計與數學
print(sum(numbers)) # 31
print(min(numbers)) # 1
print(max(numbers)) # 9
# 檢查元素是否存在
print(5 in numbers) # True
print(10 in numbers) # False
# 計數特定元素
print(numbers.count(1)) # 2
# 找出索引
print(numbers.index(4)) # 2(第一次出現的位置)新增元素
fruits = ["蘋果", "香蕉"]
# append:在尾部添加(最常用)
fruits.append("橘子")
print(fruits) # ['蘋果', '香蕉', '橘子']
# insert:在指定位置插入
fruits.insert(1, "芒果")
print(fruits) # ['蘋果', '芒果', '香蕉', '橘子']
# extend:合併另一個列表
more_fruits = ["葡萄", "西瓜"]
fruits.extend(more_fruits)
print(fruits) # ['蘋果', '芒果', '香蕉', '橘子', '葡萄', '西瓜']
# + 運算子:創建新列表
all_fruits = fruits + ["草莓", "櫻桃"]刪除元素
numbers = [1, 2, 3, 4, 5]
# pop:移除並返回指定位置的元素
last = numbers.pop() # 移除最後一個,返回 5
first = numbers.pop(0) # 移除第一個,返回 1
print(numbers) # [2, 3, 4]
# remove:移除第一個匹配的元素
numbers.remove(3)
print(numbers) # [2, 4]
# del:刪除指定位置或切片
numbers = [1, 2, 3, 4, 5]
del numbers[0] # 刪除第一個
print(numbers) # [2, 3, 4, 5]
del numbers[1:3] # 刪除切片
print(numbers) # [2, 5]
# clear:清空整個列表
numbers.clear()
print(numbers) # []排序與反轉
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
# sort():原地排序(修改原列表)
numbers.sort()
print(numbers) # [1, 1, 2, 3, 4, 5, 6, 9]
# 降序排序
numbers.sort(reverse=True)
print(numbers) # [9, 6, 5, 4, 3, 2, 1, 1]
# sorted():返回新的排序列表(不修改原列表)
original = [3, 1, 4]
sorted_list = sorted(original)
print(original) # [3, 1, 4](未改變)
print(sorted_list) # [1, 3, 4]
# reverse():原地反轉
numbers = [1, 2, 3, 4, 5]
numbers.reverse()
print(numbers) # [5, 4, 3, 2, 1]⚡ 效能分析:建立演算法思維
這是本單元最重要的部分:理解不同操作的效能。
Python List 效能表
| 操作 | 時間複雜度 | 說明 |
|---|---|---|
list[i] | $O(1)$ | 索引存取 |
list.append(x) | $O(1)$ (均攤) | 尾部添加 |
list.pop() | $O(1)$ | 尾部移除 |
list.pop(0) | $O(N)$ | ⚠️ 頭部移除(慢) |
list.insert(0, x) | $O(N)$ | ⚠️ 頭部插入(慢) |
x in list | $O(N)$ | 成員測試 |
list.remove(x) | $O(N)$ | 移除元素 |
list.sort() | $O(N \log N)$ | 排序 |
len(list) | $O(1)$ | 取得長度 |
sum(list) | $O(N)$ | 計算總和 |
效能陷阱
# ❌ 效能差:從頭部刪除(每次都需要移動所有元素)
for _ in range(n):
list.pop(0) # O(N) × N 次 = O(N²)
# ✅ 效能好:從尾部刪除
for _ in range(n):
list.pop() # O(1) × N 次 = O(N)
# ✅ 或使用 collections.deque(下個模組會學)
from collections import deque
queue = deque(list)
for _ in range(n):
queue.popleft() # O(1)為什麼這很重要?
在 APCS 中,選擇錯誤的資料結構或操作,可能導致:
- TLE(Time Limit Exceeded):執行超時
- 即使演算法正確,也無法通過測試
範例:處理 100,000 筆資料
# ❌ 錯誤:從頭部刪除 → O(N²) = 10,000,000,000 次操作
data = list(range(100000))
while data:
data.pop(0) # 每次都要移動所有剩餘元素
# ✅ 正確:從尾部刪除 → O(N) = 100,000 次操作
data = list(range(100000))
while data:
data.pop() # 只需要改變一個指標完整範例程式
範例 1:讀取並處理數列
import sys
# 讀取 N 個整數
n = int(sys.stdin.readline())
numbers = list(map(int, sys.stdin.readline().split()))
# 計算統計資訊
total = sum(numbers)
average = total / n
maximum = max(numbers)
minimum = min(numbers)
print(f"總和: {total}")
print(f"平均: {average:.2f}")
print(f"最大值: {maximum}")
print(f"最小值: {minimum}")範例 2:找出大於平均值的數字
import sys
numbers = list(map(int, sys.stdin.readline().split()))
# 計算平均值
average = sum(numbers) / len(numbers)
# 篩選大於平均值的數字
above_average = [num for num in numbers if num > average]
print(f"大於平均值 {average:.2f} 的數字:")
print(*above_average) # * 用於解包列表範例 3:移除重複元素(保持順序)
import sys
numbers = list(map(int, sys.stdin.readline().split()))
# 方法一:使用列表(保持順序)
unique = []
for num in numbers:
if num not in unique:
unique.append(num)
# 方法二:使用 set(不保持順序,但更快)
unique_set = list(set(numbers))
print("去重後(保持順序):", unique)
print("去重後(不保持順序):", unique_set)範例 4:找出連續子序列的最大和
import sys
numbers = list(map(int, sys.stdin.readline().split()))
n = len(numbers)
max_sum = numbers[0]
current_sum = numbers[0]
for i in range(1, n):
# 選擇:繼續累加 或 重新開始
current_sum = max(numbers[i], current_sum + numbers[i])
max_sum = max(max_sum, current_sum)
print(max_sum)範例 5:雙指標技巧
import sys
# 判斷列表是否為回文
def is_palindrome(lst):
left = 0
right = len(lst) - 1
while left < right:
if lst[left] != lst[right]:
return False
left += 1
right -= 1
return True
data = list(map(int, sys.stdin.readline().split()))
print("是回文" if is_palindrome(data) else "不是回文")APCS 策略與應用
對應評量項目
- 實作題 1-2 級分:陣列的基本操作
- 實作題 3 級分:陣列的進階應用(前綴和、雙指標)
常見題型
- 統計問題:計算總和、平均、最大最小值
- 搜尋問題:查找特定元素或滿足條件的元素
- 轉換問題:根據規則轉換陣列內容
- 模擬問題:模擬陣列的操作流程
實戰檢查清單
在提交程式前,檢查:
- [ ] 是否使用了
list.pop(0)或list.insert(0, x)? - [ ] 在大迴圈中是否有
x in list檢查? - [ ] 是否可以用
set加速成員測試? - [ ] 排序是否必要?能否用 $O(N)$ 算法替代?
📝 Quiz:測試你的理解
問題 1
以下程式碼的輸出是什麼?
numbers = [1, 2, 3, 4, 5]
print(numbers[-2])A. 2
B. 3
C. 4
D. 5
點擊查看解答
答案:C
解析:
負索引 -1 代表最後一個元素(5),-2 代表倒數第二個元素(4)。
問題 2
哪個操作的時間複雜度是 O(1)?
A. list.pop(0)
B. list.append(x)
C. x in list
D. list.remove(x)
點擊查看解答
答案:B
解析:
list.append(x)在尾部添加,均攤時間複雜度為 O(1)list.pop(0)需要移動所有元素,O(N)x in list需要遍歷,O(N)list.remove(x)需要查找並移動元素,O(N)
問題 3
以下程式碼創建的二維列表有什麼問題?
matrix = [[0] * 3] * 2
matrix[0][0] = 1A. 沒有問題
B. 會報錯
C. 兩行會都被修改
D. 只有第二行被修改
點擊查看解答
答案:C
解析:[[0] * 3] * 2 創建的是兩個指向同一個列表的參考(淺拷貝),所以修改 matrix[0][0] 時,matrix[1][0] 也會被修改。
正確做法:[[0] * 3 for _ in range(2)]
問題 4
numbers[::-1] 的作用是什麼?
A. 排序列表
B. 反轉列表
C. 複製列表
D. 清空列表
點擊查看解答
答案:B
解析:
切片語法 [start:stop:step],當 step 為 -1 時,表示反向遍歷,因此 [::-1] 會返回反轉後的列表。
問題 5
以下哪個方法會修改原列表?
A. sorted(list)
B. list.sort()
C. list + [1, 2, 3]
D. list[:]
點擊查看解答
答案:B
解析:
list.sort()是原地排序,會修改原列表sorted(list)返回新列表,不修改原列表list + [1, 2, 3]創建新列表list[:]創建列表的淺拷貝
🔗 推薦習題
基礎練習
練習重點
- 讀取並處理一維陣列
- 計算統計資訊(總和、平均、最大最小)
- 遍歷陣列並應用條件判斷
- 注意操作的時間複雜度
學習建議
完成這些題目後,確保你能夠:
- [ ] 熟練使用不同方式創建列表
- [ ] 正確使用索引和切片
- [ ] 選擇適當的遍歷方式
- [ ] 理解常用操作的時間複雜度
- [ ] 避免效能陷阱
💡 進階:為什麼需要其他資料結構?
List 很強大,但不是萬能的。看看這個對照表,你就會理解為什麼後續要學習其他資料結構:
| 需求 | List 效能 | 更好的選擇 | 模組 |
|---|---|---|---|
| 快速查找「元素是否存在」 | O(N) | set:O(1) | 模組二 |
| 從頭部添加/刪除 | O(N) | deque:O(1) | 模組三 |
| 快速按鍵查找值 | N/A | dict:O(1) | 模組二 |
| 先進先出(佇列) | O(N) | deque:O(1) | 模組三 |
這就是為什麼我們需要學習更多資料結構!
恭喜!模組一完成 🎉
你已經完成了模組一的所有單元!現在你應該具備:
- ✅ 高速 I/O 的習慣
- ✅ 精確的邏輯控制能力
- ✅ 處理批量資料的技能
- ✅ 基本的效能意識
你已經打好了堅實的地基!
準備好進入下一個階段了嗎?