小强要参加 NOAI 初赛了。这学期他把「算法内功」练扎实:复杂度、快排、栈队列、链表、树、递归分治、DP、贪心——这些听起来吓人的名词,将一个个变成他手里的武器。年底的模拟赛,就是检验成色的第一块试金石。
啃下数据结构与经典算法(栈队列、链表、树、DP、贪心),并针对性冲刺 NOAI 初赛。
用故事和生活类比打开这学期的大门,先「知其所以然」再学语法。
小强要参加 NOAI 初赛了。这学期他把「算法内功」练扎实:复杂度、快排、栈队列、链表、树、递归分治、DP、贪心——这些听起来吓人的名词,将一个个变成他手里的武器。年底的模拟赛,就是检验成色的第一块试金石。
不用真炒一遍菜,看「食材翻倍时时间怎么变」就知道快慢。
增长趋势最后放上去的最先被拿走(后进先出 LIFO)。
LIFO先来的先买到(先进先出 FIFO),谁也别插队。
FIFO算过的答案记在草稿纸上,下次直接查,绝不重算。
记忆化算法进阶与NOAI初赛备战 —— 共 12 课时,全部知识点一网打尽。
# 大 O 表示法:数据量翻倍,耗时怎么变?
import time
def find(lst, x): # O(n) 线性查找
for i, v in enumerate(lst):
if v == x: return i
return -1
data = list(range(10_000_000))
t = time.perf_counter()
find(data, -1) # 最坏情况:全找一遍
print("O(n) 找 1000 万个数:%.3f 秒" % (time.perf_counter() - t))
print("O(log n) 二分只需 ~24 步,O(1) 直接定位")O(n) 找 1000 万个数:0.352 秒
O(log n) 二分只需 ~24 步,O(1) 直接定位def quick_sort(a):
if len(a) <= 1: return a
pivot = a[len(a) // 2] # 选基准
left = [x for x in a if x < pivot]
mid = [x for x in a if x == pivot]
right = [x for x in a if x > pivot]
return quick_sort(left) + mid + quick_sort(right)
print(quick_sort([5, 2, 9, 1, 7, 3]))
# 归并排序思路:对半分 → 各自排好 → 合并[1, 2, 3, 5, 7, 9]# 栈:后进先出,像弹匣
def is_valid(s):
stack, pair = [], {")": "(", "]": "[", "}": "{"}
for ch in s:
if ch in "([{":
stack.append(ch)
elif not stack or stack.pop() != pair[ch]:
return False
return not stack
print(is_valid("([()])"))
print(is_valid("([)]"))True
Falseclass Node: # 一节车厢
def __init__(self, val):
self.val, self.next = val, None
head = Node("车头")
head.next = Node("车厢A")
head.next.next = Node("车厢B")
# 插入:改两根「挂钩」就行,不用挪其他车厢
new = Node("加急车厢")
new.next, head.next = head.next, new
cur = head
while cur:
print(cur.val, end=" → ")
cur = cur.next
print("尾")车头 → 加急车厢 → 车厢A → 车厢B → 尾class Node:
def __init__(self, val):
self.val, self.l, self.r = val, None, None
root = Node("爷爷")
root.l, root.r = Node("大伯"), Node("爸爸")
root.r.l = Node("我")
def preorder(n): # 前序:根 → 左 → 右
return [n.val] + preorder(n.l) + preorder(n.r) if n else []
print("前序遍历:", preorder(root))前序遍历: ['爷爷', '大伯', '爸爸', '我']def hanoi(n, a, b, c):
"""把 n 个盘从 a 借 b 挪到 c"""
if n == 1:
return [(n, a, c)]
return (hanoi(n-1, a, c, b) # ① 上面 n-1 个挪去 b
+ [(n, a, c)] # ② 最大盘直接去 c
+ hanoi(n-1, b, a, c)) # ③ 再把 n-1 个从 b 挪到 c
for mv in hanoi(3, "A", "B", "C"):
print("盘%d:%s → %s" % mv)
print("n 个盘共需 2^n - 1 步")盘1:A → C
盘2:A → B
盘1:C → B
盘3:A → C
盘1:B → A
盘2:B → C
盘1:A → C
n 个盘共需 2^n - 1 步# 每次走 1 或 2 阶,n 阶有几种走法?
def climb(n):
if n <= 2: return n
a, b = 1, 2 # dp[i-2], dp[i-1]
for _ in range(3, n + 1):
a, b = b, a + b # dp[i] = dp[i-1] + dp[i-2]
return b
print("3 阶:", climb(3), "种")
print("10 阶:", climb(10), "种")
# 递归 + 记忆化 = 同样的 O(n),自底向上更省空间3 阶: 3 种
10 阶: 89 种# 贪心:每次都拿「当前最大」面额
coins = [25, 10, 5, 1]
def change(n):
cnt, plan = 0, []
for c in coins:
k = n // c
if k: plan.append(f"{c}分×{k}")
cnt += k; n -= k * c
return cnt, plan
print(change(68))
# 注意:贪心不总是全局最优,如面额 [1,3,4] 凑 6(4, ['25分×2', '10分×1', '5分×1', '1分×3'])# 初赛高频概念,一句话分清楚:
concepts = {
"机器学习": "从数据中学规律,不用人写死规则",
"深度学习": "用多层神经网络自动提特征",
"训练集": "用来学习的题目",
"验证集": "用来调参的模拟考",
"测试集": "最终大考,只考一次",
"过拟合": "死记硬背,新题就不会",
"欠拟合": "根本没学会,老题也错",
}
for k in ["过拟合", "欠拟合"]:
print(k, ":", concepts[k])过拟合 : 死记硬背,新题就不会
欠拟合 : 根本没学会,老题也错# 编程题四步法:读题 → 举例 → 写码 → 边界
# 例:统计每个字符出现次数
s = "noicontest"
from collections import Counter
cnt = Counter(s)
print(cnt.most_common(3)) # 手写就是 dict + 循环
# 手写版:
d = {}
for ch in s:
d[ch] = d.get(ch, 0) + 1
print(sorted(d.items(), key=lambda x: -x[1])[:3])[('n', 2), ('o', 2), ('c', 1), ('t', 1)]
[('n', 2), ('o', 2), ('c', 1), ('t', 1)]# 90 分钟 100 分的典型分配:
plan = [
("选择题 2'×15", "25 分钟", "稳拿,概念题不纠结"),
("程序填空 3'×10", "30 分钟", "先在脑内跑一遍代码"),
("编程题 10'×2~3", "30 分钟", "先写暴力解保底,再优化"),
("检查", "5 分钟", "文件名、输出格式、边界"),
]
for part, t, tip in plan:
print(f"{part:<18} {t:<8} {tip}")选择题 2'×15 25 分钟 稳拿,概念题不纠结
程序填空 3'×10 30 分钟 先在脑内跑一遍代码
编程题 10'×2~3 30 分钟 先写暴力解保底,再优化
检查 5 分钟 文件名、输出格式、边界# 核心思路:每排一个数就重画一次柱状图
import random
a = [random.randint(10, 90) for _ in range(6)]
def bubble(a): # 冒泡排序(可视化最佳)
for i in range(len(a)):
for j in range(len(a) - 1 - i):
if a[j] > a[j + 1]:
a[j], a[j + 1] = a[j + 1], a[j]
yield a[:] # 每交换一次就「直播」一次
print("排序前:", a)
for snap in bubble(a):
print("一帧:", snap)排序前: [72, 15, 88, 34, 61, 9]
一帧: [15, 72, 88, 34, 61, 9]
一帧: [15, 72, 34, 88, 61, 9]
一帧: [15, 72, 34, 61, 88, 9]
……(Pygame 里每帧画柱子,肉眼看到排序过程)每个知识点都要落到能跑、能玩、能演示的作品上。
期末项目是整学期知识点的综合检验场:综合运用。学生将独立完成需求拆解 → 功能实现 → 调试打磨 → 上台演示的完整闭环。
| 课次 | 主题 | 趣味项目 | 知识点 |
|---|---|---|---|
| 1 | 时间复杂度 | 算法效率大比拼 | 大O表示法 |
| 2 | 高级排序 | 快速排序实现 | 快排、归并排序 |
| 3 | 栈与队列 | 括号匹配检查器 | 栈、队列实现 |
| 4 | 链表 | 火车车厢模拟器 | 链表操作 |
| 5 | 树与二叉树 | 家族树 | 树结构、遍历 |
| 6 | 递归进阶 | 汉诺塔游戏 | 分治思想 |
| 7 | 动态规划入门 | 爬楼梯问题 | DP思想、斐波那契优化 |
| 8 | 贪心算法 | 找零钱问题 | 贪心策略 |
| 9 | NOAI初赛精讲1 | 选择题专项 | AI基础概念 |
| 10 | NOAI初赛精讲2 | Python编程题 | 手写代码技巧 |
| 11 | NOAI初赛精讲3 | 真题模拟 | 应试技巧 |
| 12 | 期末项目 | 算法可视化工具 | 综合运用 |
把一学期的知识点串成体系,看清来路与去向。
◀ 上一章:二年级 下学期 · 完整游戏开发与数据处理
下一章 ▶:三年级 下学期 · 综合项目与阶段毕业
啃下数据结构与经典算法(栈队列、链表、树、DP、贪心),并针对性冲刺 NOAI 初赛。
结业时你将带走:3 个可玩游戏作品 + 一套算法思维 + NOAI 初赛参赛能力。
避开高频易错点,再用速查表把语法焊进肌肉记忆。
def f(n):
return f(n - 1) # 永远停不下来def f(n):
if n == 0: return 0
return f(n - 1)dp = [0] * n
# 越界或用错初始值dp = [0] * (n + 1)
dp[0], dp[1] = 1, 1对已排序数组取 lst[0] 做基准
# 每次只能分掉1个 → O(n²)import random
pivot = random.choice(lst)零钱 [1,3,4] 凑 6:
贪心 4+1+1 = 3枚 # 不是最优DP:3+3 = 2枚 ✓| 功能 | 写法 | 一句话理解 |
|---|---|---|
| 复杂度排序 | O(1) < O(log n) < O(n) < O(n log n) < O(n²) | 从快到慢 |
| 快排骨架 | def qsort(l): pivot…小于组+大于组递归 | 分治思想 |
| 栈 | stack.append(x) / stack.pop() | 压入 / 弹出(顶端) |
| 队列 | from collections import deque q.popleft() | 队首出队 O(1) |
| 二叉树遍历 | 前序 根左右 / 中序 左根右 / 后序 左右根 | 三种姿势走遍全树 |
| DP 转移方程 | dp[i] = min(dp[i-1], dp[i-2]) + cost[i] | 爬楼梯类问题 |
| 排序列表 | sorted(lst, key=lambda x: -x[1]) | 按第二项从大到小 |
按 A / B / C 三档难度自主选择:保底完成 A 组,学有余力挑战 B、C 组。