# Misty Panther
###### tags: `Easy`
> [name=出題者 : 曾致銘]
## Problem
給定一個整數 `n` ,要求印出一個長度為 `n` 的字串陣列 `answer` 滿足:
- 若 `i` 可被 `3` 和 `5` 整除,則 `answer[i-1] == "MistyPanther"`。
- 若 `i` 可被 `3` 整除,則 `answer[i-1] == "Misty"`。
- 若 `i` 可被 `5` 整除,則 `answer[i-1] == "Panther"`。
- 若不滿足以上條件,則 `answer[i-1] == i`。
### Examples
範例 1:
```
輸入: 3
輸出: 1 2 Misty
```
範例 2:
```
輸入: 5
輸出: 1 2 Misty 4 Panther
```
範例 3:
```
輸入: 15
輸出: 1 2 Misty 4 Panther Misty 7 8 Misty Panther 11 Misty 13 14 MistyPanther
```
### Constraints
- $1 \le$ `n` $\le 10^4$
### Hints
### Extra Challenge
請思考倘若今天題目給定的字串數量非常多時 (如 I Love Misty Panther 8725),你的演算法是否會看起來很繁雜?有沒有更簡潔的作法?
## Solution
本篇在討論的是如何精進你的演算法,使得在資料增加時,能夠看起來更加簡潔與易讀。
:::spoiler Description
### Approach 1: 直觀算法
#### 思路
你的直覺想法應該是,判斷 `i` 值能否能被 `3`, `5`, 或同時整除。
#### 算法
1. 創建一個長度為 `n` 的字串陣列 `answer` 作為輸出。
2. 遍歷整數 `i` 滿足 $1 \le$ `i` $\le n$。
3. 判斷如果 `i` 可同時被 `3`, `5` 整除,設 `answer[i-1] = "MistyPanther"`。
4. 否則判斷如果 `i` 可被 `3` 整除,設 `answer[i-1] = "Misty"`。
5. 否則判斷如果 `i` 可被 `5` 整除,設 `answer[i-1] = "Panther"`。
6. 否則,設 `answer[i-1] = i`
7. 重複步驟 3 到步驟 6 直至遍歷完成。
```cpp=
// 以下省略 cin, cout,僅呈現演算法部分
// 步驟 1
string answer[n];
for (int i = 1; i <= n; i++) {
bool divisibleBy3 = (i % 3 == 0);
bool divisibleBy5 = (i % 5 == 0);
if (divisibleBy3 && divisibleBy5) {
// 步驟 3
answer[i - 1] = "MistyPanther";
} else if (divisibleBy3) {
// 步驟 4
answer[i - 1] = "Misty";
} else if (divisibleBy5) {
// 步驟 5
answer[i - 1] = "Panther";
} else {
// 步驟 6
answer[i - 1] = to_string(i);
}
}
```
#### 複雜度分析
- 時間複雜度: $O(n)$。
- 空間複雜度: $O(1)$。
### Approach 2: 字串組合
#### 思路
這個算法其實不會減少數學上的計算複雜度,但若資料增加時它能夠更加簡潔。舉例而言,若今天的題目是「Misty Panther 8725」:
```
3 ---> "Misty", 5 ---> "Panther", 7 ---> "8725"
```
若你嘗試使用上一種演算法來處理,那麼你將會有很多條件要檢查:
1. 被 3 整除
2. 被 5 整除
3. 被 7 整除
4. 被 3, 5 整除
5. 被 3, 7 整除
6. 被 5, 7 整除
7. 被 3, 5, 7 整除
8. 不被整除
如此一來,當你的資料增加時,你的條件將會指數型成長。因此,與其把每一個數字的組合都判斷一次,不如只判斷個別數字的整除性,並把符合的字串「加」上題目要求的字串,如以 `i == 15` 來舉例:
```
條件 1: 15 % 3 == 0, answer[14] = "Misty"
條件 2: 15 % 5 == 0, answer[14] += "Panther"
=> answer[14] == "MistyPanther"
```
在這種情況下,我們只需要判斷 2 次而非上一演算法的 3 次,而對於「Misty Panther 8725」來說,我們也只需要判斷 3 次而非 7 次。
#### 算法
```cpp=
// 以下省略 cin, cout,僅呈現演算法部分
// 作為答案的陣列 「answer」
string answer[n];
for (int i = 1; i <= n; i++) {
bool divisibleBy3 = (i % 3 == 0);
bool divisibleBy5 = (i % 5 == 0);
string numAnsStr = "";
if (divisibleBy3) {
// 可被 3 整除,字串加上 "Misty"
numAnsStr += "Misty";
}
if (divisibleBy5) {
// 可被 5 整除,字串加上 "Panther"
numAnsStr += "Panther";
}
if (numAnsStr == "") {
// 皆無法被整除,字串加上該數字本身
numAnsStr += to_string(i);
}
// 把計算後的結果加入陣列
answer[i - 1] = numAnsStr;
}
```
#### 複雜度分析
- 時間複雜度: $O(n)$。
- 空間複雜度: $O(1)$。
### Approach 3: Hash it!
#### 思路
這個算法是上一個算法的再優化。雖然在資料沒有很多時,上個算法感覺已經很好了。但如果今天的題目設計者很邪惡,然後出了一長串的資料怎麼辦?
對於每個數字都寫一個判斷區塊是不可行的,或者說,你的程式最後會有好幾百行然後非常難以閱讀。
再者,倘若今天要改掉其中一個數字,甚至刪除其中一筆資料時,難道我們要把所有區塊都改寫嗎?
其實並不需要。我們可以把資料存在**雜湊表 (Hash Table)** 中。
#### 算法
1. 把所有資料存在一個雜湊表中。如創建 `mistyPantherHash = { 3: "Misty", 5: "Panther" }`。
2. 遍歷整數 `i` 滿足 $1 \le$ `i` $\le n$。
3. 對每個 `i` 遍歷 `mistyPantherHash` 中的每個鍵,判斷其是否可以被整除。
4. 如果步驟 3 成立,那麼把 `mistyPantherHash` 中對應的值加入組合字串中。
5. 把組合字串加入答案陣列。
> 用這種方式你就可以確保,當資料更改時,不需要更改程式內容,只需要更改雜湊表即可。
所以,對於「Misty Panther 8725」來說,它的雜湊表看起來會像是 `{ 3: "Misty", 5: "Panther", 7: "8725" }`
```cpp=
// 以下省略 cin, cout,僅呈現演算法部分
// 答案陣列
string answer[n];
// 雜湊表 mistyPantherDict
unordered_map<int, string> mistyPantherDict = {
{3, "Misty"}, {5, "Panther"}
};
// 所有除數的陣列
int divisors[] = {3, 5};
for (int i = 1; i <= n; i++) {
string numAnsStr = "";
for (auto key : divisors) {
// 若 i 可被 key 整除,就新增對應字串值進 numAnsStr 中
if (i % key == 0) {
numAnsStr += mistyPantherDict.at(key);
}
}
if (numAnsStr == "") {
// 皆無法被整除,字串加上該數字本身
numAnsStr += to_string(i);
}
// 把計算後的結果加入陣列
answer[i - 1] = numAnsStr;
}
```
#### 複雜度分析
- 時間複雜度: $O(n)$。
- 空間複雜度: $O(1)$。
### 完整解答
```cpp=
#include <iostream>
#include <unordered_map>
using namespace std;
int main() {
int n;
cin >> n;
unordered_map<int, string> mistyPantherDict = {
{3, "Misty"}, {5, "Panther"}
};
int divisors[] = {3, 5};
for (int i = 1; i <= n; i++) {
string numAnsStr = "";
for (auto key : divisors) {
if (i % key == 0) {
numAnsStr += mistyPantherDict.at(key);
}
}
if (numAnsStr == "") {
numAnsStr += to_string(i);
}
cout << numAnsStr << " ";
}
cout << endl;
}
```
:::
## Reference
https://leetcode.com/problems/fizz-buzz/