030 · Fizz Buzz
My First Thoughts
这道题是我强烈要求下的简单题,哈哈。
第一反应就是:直接一个循环应该就可以。因为题目要的是从 1 到 n 每个数字对应的结果,所以自然要把这些数字一个个看过去。
关键在于判断余数,也就是判断能不能整除:
- 能同时被
3和5整除,放"FizzBuzz" - 能被
3整除,放"Fizz" - 能被
5整除,放"Buzz" - 都不行,就放数字自己的字符串
所以可以先准备一个 answer,然后从 1 循环到 n,每次根据当前数字 val 的整除情况往里面加一个字符串:
def fizzBuzz(n):
answer = []
for val in range(1, n + 1):
if val % 15 == 0:
answer.append("FizzBuzz")
elif val % 3 == 0:
answer.append("Fizz")
elif val % 5 == 0:
answer.append("Buzz")
else:
answer.append(str(val))
return answerWhy That Is Not Enough
严格说,这个思路已经够了。Fizz Buzz 这题的核心就是模拟,没有隐藏的二分、哈希表、动态规划或者树结构。
真正需要讲清楚的只有一个点:为什么要先判断 val % 15 == 0。
如果把判断写成这样:
if val % 3 == 0:
answer.append("Fizz")
elif val % 5 == 0:
answer.append("Buzz")
elif val % 15 == 0:
answer.append("FizzBuzz")那么 15 会先命中 val % 3 == 0,结果被写成 "Fizz",后面的 "FizzBuzz" 分支根本不会执行。
所以这题不是算法难,而是分支顺序要对。一个数如果能被 15 整除,就说明它同时能被 3 和 5 整除,这种情况必须先处理。
Final Idea
最终思路可以压成一句话:
从
1到n逐个模拟,每个数字按15 -> 3 -> 5 -> 原数字的顺序判断。
这里用 val % 15 == 0 表示“同时被 3 和 5 整除”。当然,也可以写成:
val % 3 == 0 and val % 5 == 0但在这道题里,% 15 更短,也很清楚。
每次循环只负责当前这一个数字:
当前数字 val
如果 val 能被 15 整除 -> "FizzBuzz"
否则如果 val 能被 3 整除 -> "Fizz"
否则如果 val 能被 5 整除 -> "Buzz"
否则 -> str(val)
这样从 1 走到 n,答案列表自然就构造出来了。
Why It Works
题目要求返回长度为 n 的列表,第 i 个位置对应数字 i + 1。所以循环 range(1, n + 1) 正好覆盖所有需要处理的数字,不会少,也不会多。
对于每个数字,四种情况互相覆盖了所有可能:
- 同时能被
3和5整除 - 只能被
3整除 - 只能被
5整除 - 都不能整除
先判断 15,可以保证像 15、30、45 这样的数字先被归到 "FizzBuzz",不会提前落到 "Fizz" 或 "Buzz"。
如果前三个整除条件都不满足,那这个数字就应该保留原样。但答案要求是字符串列表,所以要用 str(val),不能直接放整数 val。
Code
def fizzBuzz(n):
answer = []
for val in range(1, n + 1):
if val % 15 == 0:
answer.append("FizzBuzz")
elif val % 3 == 0:
answer.append("Fizz")
elif val % 5 == 0:
answer.append("Buzz")
else:
answer.append(str(val))
return answerComplexity
| Time | \(O(n)\) - 从 1 到 n 每个数字处理一次 |
| Space | \(O(n)\) - 返回的 answer 本身有 n 个字符串 |
Takeaway
有些 Easy 题的“正解”就是把题目规则稳定地翻译成代码。Fizz Buzz 重点不是找更复杂的方法,而是确认循环范围、判断顺序、返回类型这三件事都和题意一致。遇到“同时满足 A 和 B”的分支时,通常要先判断更具体的情况,再判断更普通的情况。
← Quiz