LeetCode 双刀流: 412. FizzBuzz

412. FizzBuzz

412. FizzBuzz 是一个相当经典的题目,号称是 Google 面题题 之一,这个题目虽然看似简单的但有许多细节值得深究。

先看一下题目描述

Given an integer n, return a string array answer (1-indexed) where:

answer[i] == "FizzBuzz" if i is divisible by 3 and 5.
answer[i] == "Fizz" if i is divisible by 3.
answer[i] == "Buzz" if i is divisible by 5.
answer[i] == i if non of the above conditions are true.

再搭配范例理解题目

  • Example 1:
Input: n = 3
Output: ["1","2","Fizz"]
  • Example 2:
Input: n = 15
Output: ["1","2","Fizz","4","Buzz","Fizz","7","8","Fizz","Buzz","11","Fizz","13","14","FizzBuzz"]

让使用者输入一个数字 n,产生一个从 1 到 n 的容器,当遇到三的倍数改成 Fizz、 五的倍数改成 Buzz、同时满足改成 FizzBuzz。

看完这个题目描述之後,你是不是心想「这麽简单的题目,不就是条件判断吗?」,那就让我们继续看下去!

开始实作

在理解完题目与条件之後,那我们下一步就开始「#初探直觉解」并且一起尝试「#刻意优化」的过程:

方法 ①:暴力法

第一种想法就是真的从 1 开始跑一个回圈,用三个条件分别判断「三的倍数」、「五的倍数」和「同时满足三和五的倍数」。唯一要注意的点就是「同时满足三和五的倍数」条件比较严格,所以要放在前面作判断。

那我们先用 Python 实作看看

class Solution:
    def fizzBuzz(self, n):
        ans = []

        for num in range(1,n+1):
            if num % 3 == 0 and num % 5 == 0:
                ans.append("FizzBuzz")
            elif num % 3 == 0:
                ans.append("Fizz")
            elif num % 5 == 0:
                ans.append("Buzz")
            else:
                ans.append(str(num))

        return ans

也可以用 JavaScript 复刻一次

var fizzBuzz = function(n) {
  let ans = [];
  
  for (let i = 1; i <= n; i++) {
    if (i % 15 === 0) {
      ans.push('FizzBuzz');
    } else if (i % 5 === 0) {
      ans.push('Buzz');
    } else if (i % 3 === 0) {
      ans.push('Fizz');
    } else {
      ans.push(i + '');
    }
  }
  return ans;
};

方法 ②:字串组装

试着思考一下,本题有两个数的倍数,所以有三个;但如果题数有三个数字,当遇到三的倍数改成 Fizz、 五的倍数改成 Buzz、七的倍数变成 Jazz 的情况,那需要考虑的规则就有:

  1. Divisible by 3
  2. Divisible by 5
  3. Divisible by 7
  4. Divisible by 3 and 5
  5. Divisible by 3 and 7
  6. Divisible by 7 and 3
  7. Divisible by 3 and 5 and 7
  8. Not divisible by 3 or 5 or 7.

这就是所谓的条件会变成指数的成长,第一种方法会造成程序码撰写的复杂度急剧上升。根据观察之後,我们发现这个转换规则会有先後的顺序关系:

  1. Divisible by 3 => Fizz
  2. Divisible by 3 and 5 => FizzBuzz
  3. Divisible by 3 and 7 => FizzJazz
  4. Divisible by 3 and 5 and 7 => FizzBuzzJazz

因此,我们想到的方法可以利用字串组装的方式,几个数字只需要跑几个条件:

s = ''
if num % 3 == 0:
    s += "Fizz"
if num % 5 == 0:
    s += "Buzz"

那我们先用 Python 实作看看


class Solution:
    def fizzBuzz(self, n):
        ans = []

        for num in range(1,n+1):

            s = ""

            if num % 3 == 0:
                s += "Fizz"
            if num % 5 == 0:
                s += "Buzz"
            if not num_ans_str:
                s = str(num)

            ans.append(s)  

        return ans

也可以用 JavaScript 复刻一次

var fizzBuzz = function(n) {
  
  let ans = [];
  
  for (let i = 1; i <= n; i++) {
    
    let s = '';
    
    if (i % 3 === 0)
      s += 'Fizz';
    if (i % 5 === 0)
      s += 'Buzz';
    if (!str)    
      s = i + '';
    
    ans.push(s);
  }
  
  return ans
};

方法 ③:Hashing

刚刚的想法是,如果 n 个数字需要有 n 个判断,那要如何可以自动化产生 n 个判断呢?可以把 n 个数字的转换关系写成一个 Hashmap,像这样子:

d = {3 : "Fizz", 5 : "Buzz"}
for key in d.keys():
    if num % key == 0:
        s += d[key]

那我们先用 Python 实作看看

class Solution:
    def fizzBuzz(self, n):

        ans = []
        d = {3 : "Fizz", 5 : "Buzz"}

        for num in range(1,n+1):
            s = ""

            for key in d.keys():
                if num % key == 0:
                    s += d[key]
            if not num_ans_str:
                s = str(num)

            ans.append(s)  

        return ans

也可以用 JavaScript 复刻一次

let fizzBuzz = function(n) {
  
  let ans = [];
  let d = { 3: 'Fizz', 5: 'Buzz', ...};
  
  for (let i = 1; i <= n; i++) {
    let s = '';
    for (let key in d) {
      if (i % parseInt(key, 10) === 0) {
        s += d[key];
      }
    }

    if (!s) {
      s += i;
    }

    ans.push(s);
  }

  return result;
};

方法 ④:炫技法

最後一种方法是利用「三元运算子」或「模数运算」做优化,这边就当成一种火力展力给大家看看就好!

那我们先用 Python 实作看看

class Solution:
    def fizzBuzz(self, n):
        ans = []
        for n in range(1, N+1):
            ans.append(int(not(n % 3)) * 'Fizz' + int(not(n % 5)) * 'Buzz' or n)
        return ans

也可以用 JavaScript 复刻一次

var fizzBuzz = function(n) {
  let ans = [];
  for (let i = 1; i <= n; i++) {
    s = ((i % 3 == 0 ? 'Fizz' : '') + (i % 5 == 0 ? 'Buzz' : '')) || (i + '');
    ans.push(s);
  }
  
  return ans
};

反思沉淀

这个题目绝对不是一个难题,决大部分会写程序的人一定写得出来!但这个过程中的几种延伸思考跟优化技巧反而就蛮有趣的,这就是我前面强调「#刻意优化」的例子。能写出一个程序不难,但要写出一个好的程序很难非常难。当你从「方法 ④:炫技法」再回头看「方法 ①:暴力法」应该就可以明显看受到「好」程序的的差异。所以这个题目虽然看似简单的但有许多细节值得深究,也希望大家在一个例子中一起感受优化的过程。

举一反三看看相似题

最後可以从题目提供的相似题看一下有哪些类似的题目,适合作为你下一个尝试的练习:


嗨,大家好!我是维元,一名擅长网站开发与资料科学的双栖工程师,斜杠於程序社群【JSDC】核心成员及【资料科学家的工作日常】粉专经营。目前在 ALPHACamp 担任资料工程师,同时也在中华电信、工研院与多所学校等单位持续开课。拥有多次大型技术会议讲者与竞赛获奖经验,持续在不同的平台发表对 #资料科学、 #网页开发 或 #软件职涯 相关的分享,邀请你加入 订阅 接收最新资讯。


<<:  Day8:今天来谈一下Parrot Security上的Metasploit工具

>>:  [机派X] Day 10 - 寒酸的无人机介绍

Day_11 : 让 Vite 来开启你的 Vue 之 Config 常见配置 (Vite 最终篇 XD)

Hi Dai Gei Ho~ 我是Winnie~ 延续上篇没有说完的内容,今天我们要来看看 Vite...

Day 30-完赛结论,所有公有云的问题,我一率建议 Terraform

本篇是 30 天铁人赛的最後一篇,本篇做个小节与心得 课程内容与代码会放在 Github 上: ht...

JWT实作(三)(Day7)

我们现在设定两种权限,管理员(ADMIN)&正常(NORMAL) 要实作权限功能,我们先在u...

Wentz QOTD: CISSP练习题

在IT行业工作了26年左右之後,我在2018年成功实现了年度目标,在9个月内通过了19项考试,重新...

【Day17】电子商务与行销篇-部落格

#odoo #开源系统 #数位赋能 #E化自主 部落格是网站功能的一种型态,与一般网页较不同的地方是...