###### tags: `JavaScript` `leetcode`
# [week 2] 先別急著寫 leetcode - 虛擬碼、Debugger、解題技巧
> 本篇為 [[ALG101] 先別急著寫 leetcode](https://lidemy.com/p/alg101-leetcode) 這門課程的學習筆記。如有錯誤歡迎指正。
> [程式解題新手入門注意事項 - Huli](https://blog.huli.tw/2019/11/01/before-start-leetcode/)
---
## 寫程式≠寫程式碼
重點是在寫程式之前,先想好要怎麼做,整理自己的想法,並轉換成程式碼表達。
- 初學者寫程式
1.邊寫程式碼邊想怎麼解
2.試著套用自己以為學過的語法
- 會寫程式的人寫程式
1.先想解法,在腦中構思(太快看不出來)
2.把解法轉換成程式碼
### 初期如何學習寫程式?
- 步驟
1. 大概想一下解法,不寫程式碼
2. 把解法寫成 pseudo code(虛擬碼)
3. 把 pseudo code 翻譯成程式碼
- 利用條列式寫法
1. 將大問題分割成小問題
2. 一行只做一件事
3. 善用敘述、條件判斷
4. 善用跳轉(jump)來實現重複執行
### 範例:印出 1~100 之間的偶數
- 步驟一:如何印出 1~100?
1. 令 i 為 1 // 設置初始條件
2. 如果 i>100,結束 // 判斷是否該結束程式(終止條件)
3. 印出 i
4. i = i+1
5. 跳回第 2 行 // 重複動作
- 步驟二:如何判斷某個數是偶數?
1. 令 i = 1
2. 如果 i>100,結束
3. 如果 i % 2 ===0,印出 i // 利用是否能整除2來判斷奇偶
4. i = i+1
5. 跳回第 2 行
## 如何撰寫 Pseudo Code(虛擬碼)?
虛擬碼是程式碼與想法之間的橋樑,把解法用抽象的方式表示。
將剛才步驟二得到的可執行步驟翻成英文,會發現和程式碼非常相似:
```
1. let i = 1
2. if i>100 then exit
3. if i % 2 no remainder, print i
4. i = i+1
5. jump to line 2
```
更像程式碼的虛擬碼:
```
1. for (i from 1 to 100) do // for迴圈,有數字i會從 1到 100,do代表迴圈內動作
2. if i / 2 no remainder, print i
3. end for // 此迴圈結束
```
若以 JavaScript 撰寫成程式碼:
```
for (var i = 1; i< 100; i++) {
if(i % 2 === 0){
console.log(i)
}
}
```
### 實戰練習:印出 1-100 的奇數
利用「縮排」來標明條件判斷的區塊,較容易閱讀分析。
```
1. for (i from 1 to 100) do
2. if (i mod 2 === 1) then // mod(modulo):取餘數,餘數為 1 代表為奇數
3. print i
4. end if
5. end for
```
### 實戰練習:fiszz buzz
題目:
給一個數字 n
印出 1~n
但如果碰到 3 的倍數,改印 Fizz
碰到 5 的倍數,,改印 Buzz
若同時是 3 跟 5 的倍數,印 FizzBuzz
範例:n=7
```
1
2
Fizz
4
Buzz
Fizz
7
```
基本解法:
```
for (i from 1 to n) do // 先寫出 1~n的迴圈
if (i % 15 === 0) print “FizzBuzz”
else if (i % 3 === 0) print “Fizz”
else if (i % 5 === 0) print “Buzz”
end for
```
但此解[並非最佳解法](),若條件多 7 的倍數或更多,需要考慮的條件判斷也更複雜。
### 實戰練習:找最小值
題目:
假設今天有一副撲克牌
一次只能看一張牌
要怎麼找到最小的牌?
解法(虛擬碼):
```
let 最小的牌 = 第一張牌
for (i from 1 to n) do // n為撲克牌總數
翻開第 i 張牌
if (第 i 張排比最小牌的還小) do
最小的牌 = 第 i 張牌
end if
end for
```
將上述解法翻譯成陣列:
```
let min = arr[0] // min是陣列中最小值,0代表array第一個元素
for (i from 0 to n-1) do // n-1:因為array的 index(索引)所以從 0 開始
if (arr[i] < min) do // 如果第 i 個元素比最小值還小
min = arr[i] // i 就變成最小值
end if
end for
```
延伸閱讀:
[簡單的 FizzBuzz 藏有 深度(google 面試題)](https://medium.com/@Bear_/%E7%B0%A1%E5%96%AE%E7%9A%84-fizzbuzz-%E8%97%8F%E6%9C%89-%E6%B7%B1%E5%BA%A6-google-%E9%9D%A2%E8%A9%A6%E9%A1%8C-f5e66e3a97be)
[[課程學習筆記 ]先別急著寫 leetcode — 一步步打造程式腦](https://medium.com/@miahsuwork/%E8%AA%B2%E7%A8%8B%E5%AD%B8%E7%BF%92%E7%AD%86%E8%A8%98-%E5%85%88%E5%88%A5%E6%80%A5%E8%91%97%E5%AF%AB-leetcode-%E4%B8%80%E6%AD%A5%E6%AD%A5%E6%89%93%E9%80%A0%E7%A8%8B%E5%BC%8F%E8%85%A6-7ed12aaa4a3d)
---
## 學會像電腦一樣思考
寫程式之前,先學會「看程式」,所謂的看程式碼 = 理解程式碼如何運作。
### 以陣列加總的虛擬碼為例
```
let sum = 0
for (i from 0 to n-1) do
sum += arr[i]
end for
print sum
```
- 看懂程式碼:
1. 假設array 為 [1, 2, 3],代表陣列長度 n = 3
2. 設 sum = 0
3. 讓 i 從 0 跑到 n-1(也就是 2)
4. i現在是 0
5. sum += arr[0],sum 變成 1
6. 下一個迴圈
7. i 現在是 1
8. sum += arr[1],sum 變成 1+2 = 3
9. 下一個迴圈
10. i 現在是2
11. sum += arr[2],sum 變成 3+3 = 6
12. 下一個迴圈
13. i 現在是 3,超出 n-1,結束
14. 輸出 sum
### 人體編譯器
編譯(Compile):將高階語言所編寫的原始程式,透過編譯器(Compiler)轉成機械碼。
### 以找最大值為例
- 翻譯虛擬碼:
```
let max = arr[0]
for (i from 0 to n-1) do
if (arr[i] > max) do
max = arr[i]
end if
end for
print max
```
1. 假設 arr 為 [2, 7, 5]
2. 設 max 為 arr[0],也就是 2
3. 讓 i 從 0 跑到 2
4. i 現在是 0
5. 判斷 arr[0] 是否 >2
6. 不是
7. 下一個迴圈
8. i 現在是 1
9. 判斷 arr[1] 是否 > 2
10. 是,設 max = arr[2],也就是 7
11. 下一個迴圈
12. i 現在是 2
13. 判斷 arr[2] 是否 > 7
14. 不是
15. 下一個迴圈
16. i 現在是 3,超出條件,結束
17. 輸出 max
接著翻譯真的程式碼(JavaScript):
```js
let max = arr[0]
for (let i=0; i<arr.length; i++) { // i=0 為初始條件;終止條件;每圈執行完 i+=1
if (arr[i] > max) {
max = arr[i]
}
}
console.log(max)
```
1. 假設 arr 為 [2, 7, 5]
2. 設 max 為 arr[0],也就是 2
3. 讓 i 從 0 跑到 2
4. i 現在是 0
5. 判斷 arr[0] 是否 >2
6. 不是
7. 下一個迴圈
8. i 現在是 1
9. 判斷 arr[1] 是否 > 2
10. 是,設 max = arr[2],也就是 7
11. 下一個迴圈
12. i 現在是 2
13. 判斷 arr[2] 是否 > 7
14. 不是
15. 下一個迴圈
16. i 現在是 3,超出條件,結束
17. 輸出 max
> 重點:當不知道程式碼錯在哪時,可以一行一行檢視程式碼,驗證程式碼是否符合實際想表達的。
---
## Debug 神器:Debugger(偵錯)
利用 Chrome Devtool(開發者工具)Debugger,直接設置中斷點(break point),一行一行執行程式碼以找出除錯範圍。
### 前置作業
1. 將程式碼貼到一個 html 檔案
2. 在 `<script>` 標籤(以執行 JavaScript)底下加上 `debugger`
```js
<script>
debugger
let arr = [2, 7, 5];
let max = arr[0];
for (let i=0; i<arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
console.log(max);
</script>
```
3. 用 Chrome 開啟檔案,以開發者工具進行檢視,在 Sources 欄位點選 Step(F9),即可一步一步執行程式碼,進行除錯
![](https://i.imgur.com/lLFpAEk.png)
## Log 大法
另一個 debug 的方法,也就是「log 加好加滿」,透過 log 可以得知每個階段的值。
舉例:
```js
<script>
let arr = [2, 7, 5];
let max = arr[0];
console.log('max:', max)
for (let i=0; i<arr.length; i++) {
console.log('i=', i, 'arr[i]=', arr[i])
if (arr[i] > max) {
console.log('found new max!')
console.log('arr[i]:', arr[i], 'old max:', max)
max = arr[i];
console.log('new max:', max)
}
}
console.log(max);
</script>
```
檢視 console 欄位結果如下,可以檢視程式碼運作是否和所想的相同,是否有進入迴圈等等。
![](https://i.imgur.com/5jJvOFQ.png)
### 補充:為什麼用 console.log 印出來的值和想像中不一樣?
舉例:
```js
<script>
let arr = [2, 7, 5]; // [2, 7 , 5]
let max = arr[0];
for (let i=0; i<arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
arr.push(3); // [2, 7 , 5, 3] 註解是 Chrome 認為的 log
console.log(max);
</script>
```
#### 原因:
前後 `arr` 均為同一個變數,而 Chrome 會顯示該數最新儲存的值(抓取當下時間點的 log),而不是該 log 時間點的值。這個問題會出現在陣列和物件上。
#### 解決方法:
利用 `JSON.stringify()` 將陣列或物件轉成「字串」印出,也就是深拷貝(Deep Copy)。
參考資料:
1. [從Debugger學除錯](https://www.ithome.com.tw/voice/106635)
2. [六角學院 — Chrome 網頁除錯功能大解密](https://www.udemy.com/course/chrome-devtools/)
3. [你需要注意的 console.log 問題](https://blog.huli.tw/2020/03/23/console-log-bug/)
4. [[Javascript] 關於 JS 中的淺拷貝和深拷貝](https://larry850806.github.io/2016/09/20/shallow-vs-deep-copy/)
---
## 解題前需注意「輸入範圍」
在開始寫程式碼之前,必須注意輸入範圍,因為「不同範圍可能有不同限制」,而範圍會決定解題方法。比如說排列一百萬個數字跟排序十個數字,用的方法會不相同。
在不同限制中,需注意可能會遇到下列幾種狀況:
### 1. 空間限制
首先,對空間有個概念:
int: 4 bytes
double: bytes
JS 中的 Number: 8 bytes
由此可推算出,一百萬個數字所需記憶體:
(8 * 1e6 / 1024) = 7812 KB = 7.6 MB
那麼,十億個數字所需記憶體:
(8 * 1e8 / 1024) = 781200 KB = 7600 MB = 7.4 GB
由估算過程可知,我們不可能在程式語言中,宣告一個十億個元素儲存在陣列。
勢必得用其他方法,比如把排列好的數字先放在其他檔案等等。
### 2. 時間限制
對初學題目來說不太重要(先求有再求好),之後會再來談 big O,用來估算時間。
### 3. 型態限制
- int:-214783648 ~ 2147483648
- JS 數字: Number.MAX_SAFE_INTEGER:JavaScript 能儲存的最大值為 16 位數字,在超過這個範圍的運算可能會有誤差,例如:
```
Number.MAX_SAFE_INTEGER + 2 > Number.MAX_SAFE_INTEGER + 1
// false
Number.MAX_SAFE_INTEGER + 2 === Number.MAX_SAFE_INTEGER + 1
// true
```
- 浮點數精準度問題:這和電腦底層儲存數字的方式有關,許多程式語言都會有這個現象
```
0.1 + 0.2 === 0.3
// false
// 0.1 + 0.2 會印出的值為 0.30000000000000004
```
> 把範圍講清楚的題目,才是好題目。
> 沒有的話就問清楚!因為範圍決定解決的方法。
---
## 解題技巧:函式填空法
> 目的:簡化雙層迴圈,也就是切割問題。
### 判斷質數
以「[如何判斷質數](https://oj.lidemy.com/problem/1020)」為例:
> 印出陣列中的質數 = 印出陣列 + 質數
Step 1. 簡化問題:不知道怎麼寫的部分,先寫成函式 isPrime
```js
// 第一部分:主要邏輯
for (let i = 0; i < n; i++) {
if (isPrime(arr[i])) { // 變成函式:如果 arr[i] 是質數
console.log("Prime")
} else {
console.log("Composite")
}
}
function isPrime(n) {
// TO DO
}
```
Step 2. 實作判斷質數邏輯
```js
// 第二部分:輔助函式
function isPrime(n) {
if (n === 1) return false // 1 不是函式,先去除
for (let i = 2; i < n; i++) {
if (n % i === 0) {
return false
}
}
return true // 注意放在迴圈外,前面都不符合,才符合質數
}
```
Step 3. 合併結果
```js
function isPrime(n) {
if (n === 1) return false
for (let i = 2; i < n; i++) {
if (n % i === 0) {
return false
}
}
return true
}
for (let i = 0; i < n; i++) {
if (isPrime(arr[i])) {
console.log('Prime')
} else {
console.log('Composite')
}
}
```
試著把 function 加回去,程式碼會複雜很多:
Step 1. 把函式拿掉,維持主要邏輯不便
```js
for (var i=0; i<n; i++) {
let isPrime = true
if (isPrime) {
console.log("Prime")
} else {
console.log("Composite")
}
}
```
Step 2. 加上判斷質數程式碼
```js
// 雙層迴圈
for (var i=0; i<n; i++) {
let isPrime = true
if (arr[i] === 1) {
isPrime = false
}
if (var j=2; j<arr[i]; j++) { // 用 j 表示,是因為前面已出現變數 i
if (arr[i] % j === 0) {
is Prime = false
break
}
}
if (isPrime) {
console.log("Prime")
} else {
console.log("Composite")
}
}
```
## 解題技巧:簡化法
### 印出星星
以「[如何印出好多星星](https://oj.lidemy.com/problem/1021)」為例:
> 目的:把大問題變成小問題。如果難的寫不出來,就先寫簡單的。
Step 1. 不會輸出 n 個星星,就先輸出一個就好
```js
for (let i=1; i<=N; i += 1) {
console.log('*')
};
// 印出每行都有 *
```
Step 2. 搭配函式填空法
```js
for (let i=1; i<=N; i += 1) {
printStar(i)
};
function printStar(n) {
// TODO: 印出 n 個星星
}
```
Step 3. 利用迴圈重複加上星星
```js
for (let i = 1; i<=N; i += 1) {
printStar(i)
};
function printStar(n) {
let s = '';
for (i = 1; i <= n; i+= 1) {
s += '*'
}
console.log(s)
}
```
也可直接利用 `repeat` 函式來解題:
### 實戰:印出金字塔
預期輸出:
n = 1
```
*
```
n = 2
```
*
***
```
n = 3
```
*
***
*****
```
根據觀察可知規律為:
1. 一共有 n 層
2. 第 i 層會有 2i -1 個星星
3. 星星會置中
4. 需要 n - i 個空白
接著利用函式填空法:
```js
for (let i = 1; i <= n; i += 1) {
printLayer(i, n)
}
function printLayer(i, n) {
}
```
加上一個輔助的函式:
```js
function repeat(str, n) {
let s = ''
for (let i = 1; i <= n; i += 1) {
s += str
}
return s
}
```
再按照規律把答案填上去:
```js
for (let i = 1; i <= n; i += 1) {
printLayer(i, n)
}
function printLayer(i, n) {
// 空白 + 星星
let str = repeat(' ', n - i) + repeat('*', 2*i - 1)
console.log(str)
}
function repeat(str, n) {
let s = ''
for (let i = 1; i <= n; i += 1) {
s += str
}
return s
}
```
### 實戰:九九乘法表
預期輸出:
```
1*1=1
1*2=2
...
1*9=9
2*1=2
...
9*9=81
```
首先用簡化法,產生 `1*1 ~ 1*9`:
```js
for (let i = 1; i <= 9; i += 1) {
console.log('1*' + i '=' + 1*i )
}
```
包成函式,把 1 換成 k:
```js
function printTable(k) {
for (let i = 1; i <= 9; i += 1) {
console.log(k + '*' + i + '=' + k*i )
}
}
printTable(1)
printTable(2)
...
printTable(9)
// 即可產生九九乘法表
```
其實就是再一個迴圈:
```js
function printTable(k) {
for (let i = 1; i <= 9; i += 1) {
console.log(k + '*' + i + '=' + k*i )
}
}
// 這個會產生 1~9
for (let k = 1; k <= 9; k += 1) {
printTable(k)
}
// 結果:產生 1*1~9*9
```
但總有一天要學會雙層迴圈,試著把上面的寫法合併:
```js
// 這個產生 1~9
for (let k = 1; k <= 9; k += 1) {
// 這個也產生 1~9
for (let i = 1; i <= 9; i += 1) {
console.log(k + '*' + i + '=' + k*i )
}
}
```
我們可以利用 Debugger 來觀察雙層迴圈如何執行。也就是先執行外層迴圈第一圈,然後進入內層迴圈第一到九圈,執行完再回到外層迴圈第二圈,依序執行下去,用文字表示如下:
```
k=1 i=1, 2, 3...9
k=2 i=1, 2, 3...9
k=3 i=1, 2, 3...9
...
k=9 i=1, 2, 3...9
```
### 實戰:印出 1~100 平方數
簡化法:先印出 1~100
```js
for (let i = 1; i <= 100; i += 1) {
console.log(i)
}
```
搭配函式填空法
```js
for (let i = 1; i <= 100; i += 1) {
if (isSquare) {
console.log(i)
}
}
function isSquare(n){
}
```
那麼,該如何判斷平方數呢?
開根號:`Math.sqrt(n)`
無條件捨去:`Math.floor(n)`
將 n 開根號後得到的數字,無條件捨去後再相乘,若還是等於 n 則為平方數。
```js
for (let i = 1; i <= 100; i += 1) {
if (isSquare) {
console.log(i)
}
}
function isSquare(n){
let root = Math.floor(Math.sqrt(n))
return root * root === n // 成立得到 true;反之得到 false
}
```
另一種想法:印出平方數,直到超過 100 為止。
```js
let i=1
while (i*i <= 100) {
console.log(i*i)
i += 1
}
```
### 作業檢討:印出聖誕樹
[LIOJ 1023:印出聖誕樹](https://oj.lidemy.com/problem/1023?_ga=2.105028611.1291128142.1593271357-1977277931.1590338596)
```js
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
});
const lines = [];
// 讀取到一行,先把這一行加進去 lines 陣列,最後再一起處理
rl.on('line', (line) => {
lines.push(line);
});
function isPrime(n) {
if (n === 1) {
return false;
}
for (let i = 2; i < n; i += 1) {
if (n % i === 0) {
return false;
}
}
return true;
}
// 前面已宣告過 lines,這裡改用 input 作為參數,拿取內容 lines
function solve(input) {
let n = Number(input[0])
for (let i=1; i <= n ; i += 1) {
printTree(i, n)
}
for (let i = 1; i <= n - 1; i += 1) {
printBottom(n)
}
}
function printTree(i, n) {
console.log(
repeat(' ', n - i) +
repeat('*', 2*i - 1)
)
}
function printBottom(n) {
console.log(repeat(' ', n - 1) + '|')
}
function repeat(str, n) {
let result = ''
for (let i = 1; i <= n; i += 1) {
result += str
}
return result
}
// 輸入結束,開始針對 lines 做處理
rl.on('close', () => {
solve(lines);
});
```
### 作業檢討:NN 乘法表
[LIOJ 1024:NN 乘法表](https://oj.lidemy.com/problem/1024?_ga=2.109629445.1291128142.1593271357-1977277931.1590338596)
```js
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
});
const lines = [];
// 讀取到一行,先把這一行加進去 lines 陣列,最後再一起處理
rl.on('line', (line) => {
lines.push(line);
});
// 前面已宣告過 lines,這裡改用 input 作為參數,拿取內容 lines
function solve(input) {
let N = Number(input[0])
let M = Number(input[1])
// 產生 1~N
for (let k = 1; k <= N; k += 1) {
// 產生 1~M
for (let i = 1; i <= M; i += 1) {
console.log(k + '*' + i + '=' + k * i)
// 也可寫成 console.log(`${k}*${i}=${k*i}`)
}
}
}
// 輸入結束,開始針對 lines 做處理
rl.on('close', () => {
solve(lines);
});
```
### 作業檢討:水仙花數
[LIOJ 1025:水仙花數](https://oj.lidemy.com/problem/1025?_ga=2.109629445.1291128142.1593271357-1977277931.1590338596)
水仙花數(Narcissistic number)定義為:「一個 n 位數的數字,每一個數字的 n 次方加總等於自身」。
例如說 153 是三位數,而 1^3 + 5^3 + 3^3 = 153。
而數字 0~9 也都是水仙花數,因為一位數 n 的 1 次方一定會等於自己。
解題難點:
1. 如何判斷幾位數
2. 如何取出各個數字
可直接判斷該數字是幾位數,但這個方法侷限於「需已知輸入範圍」,也就是必須知道有幾位數:
```js
// 回傳數字幾位數
function digitsCount(n) {
if (n < 10) {
return 1
} else if (n < 1e2) { // 1e2 代表 100 科學記號寫法
return 2
} else if (n < 1e3) {
return 3
} ...
}
```
如何判斷幾位數:一直 `/ 10`
```js
// 回傳數字幾位數
function digitsCount(n) {
if (n === 0) return 1
let result = 0
while (n != 0) {
n = Math.floor(n / 10) // 無條件捨去
result += 1
}
return result
}
```
取出各個數字:`% 10` 搭配 `/ 10`
```js
function isNarcissistic(n) {
// 幾位數,宣告一個 m = n
let m = n
let digits = digitsCount(m)
let sum = 0
while (m != 0) {
let num = m % 10
// 可改成 Math.pow(num, digits) => num 的 digits 次方
sum += num**digits
m = Math.floor(m / 10)
}
// 可簡化成:return sum === n
if (sum === n) {
return true
} else if {
return false
}
}
// 應用上方兩個函式即可完成輸出
function solve(input) {
// 輸入 5 200 => ['5', '200']
const temp = input[0].split(' ');
let n = Number(temp[0]);
let m = Number(temp[1]);
for (let i = n; i <= m; i += 1) {
if (isNarcissistic(i)) {
console.log(i);
}
}
}
```
也可以把「數字轉成字串」,直接用字串來判斷幾位數:
例如:
```
'1234'.length => 4
'1234'.[0] => 1
```
```js
// 實際操作
function isNstr(n) {
// 數字 + 空字串 => 字串
// 或是 n.toString()、String(n)
let str = n + ''
let digits = str.length
let sum = 0
for (let i = 0; i < str.length;; i+= 1) {
sum += Number(str[i])**digits
}
return sum === n
}
```
### 實戰:判斷等差數列
判斷 [1, 3, 5, 7, 9] 是否為等差數列
解題技巧:
1. 先求公差:`arr[1] - arr[0]`
2. 再判斷 `arr[i] - arr[i-1]` 是否等於公差
```js
// 判斷是否為等差數列
function isValid(arr) {
let d = arr[1] - arr[0]
for (let i = 1; i < arr.length; i += 1) {
if (arr[i] - arr[i-1] !== d) {
return false
}
}
return true
}
console.log(isValid([1, 3, 5, 7, 9])) // true
console.log(isValid([1, 1, 1])) // true
console.log(isValid([1, 2, 4])) // false
```
解題時需考慮到 Edge case(邊緣條件):
1. 空陣列
2. 只有一個元素的陣列
```
function isValid(arr) {
// 涵蓋邊界條件,可避免不合法的存取
if (arr.length <= 1) return true
let d = arr[1] - arr[0]
for (let i = 1; i < arr.length; i += 1) {
if (arr[i] - arr[i-1] !== d) {
return false
}
}
return true
}
```
### 實戰:數字位數加總
與「判斷水仙花數」題型類似。
解法一:數學解
```js
function addDigits(n) {
// 若 n 為負數,乘上 -1 變成正數
if (n < 0) {
n = n * -1
}
let sum = 0
while (n != 0) {
sum += n % 10
n = Math.floor(n / 10)
}
return sum
}
console.log(addDigits(123))
```
解法二:字串偷吃步
```js
function addDigits(n) {
let str = n + ''
let sum = 0
for (let i = 0; i < str.length; i += 1) {
sum += Number(str[i])
}
return sum
}
console.log(addDigits(123))
```
---
## 經典題型
### [LIOJ 1026:判斷等比數列](https://oj.lidemy.com/problem/1026)
基本上和判斷等差數列作法類似:
```js
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
});
const lines = [];
// 讀取到一行,先把這一行加進去 lines 陣列,最後再一起處理
rl.on('line', (line) => {
lines.push(line);
});
// 判斷等比數列
function inValid(arr) {
let r = arr[1] / arr[0]
for (let i = 1; i < arr.length; i += 1) {
if (arr[i] / arr[i - 1] !== r) {
return false
}
}
return true
}
// 前面已宣告過 lines,這裡改用 input 作為參數,拿取內容 lines
function solve(input) {
let n = Number(input[0])
let arr = input[1].split(' ') // ['3', '9', '27'] => 得到字串的陣列
if (inValid(arr)) {
console.log('Yes')
} else {
console.log('No')
}
}
// 輸入結束,開始針對 lines 做處理
rl.on('close', () => {
solve(lines);
});
```
### [LIOJ 1027:信用卡號驗證](https://oj.lidemy.com/problem/1027)
### [LIOJ 1028:生命靈數](https://oj.lidemy.com/problem/1028)
```js
// 與位數加總做法相同
function addDigits(n) {
let sum = 0
while (n != 0) {
sum += n % 10
n = Math.floor(n / 10)
}
return sum
}
function solve(input) {
// ['1991 11 7'] = > ['1991', '11', '7']
let temp = input[0].split(' ')
// 1991117
let num = Number(temp[0] + temp[1] + temp[2])
let p = addDigits(num)
// 判斷是否不只一位
while (p >= 10) {
p = addDigits(p)
}
console.log(p)
}
```
### [LIOJ 1029:加減乘除](https://oj.lidemy.com/problem/1029)
```js
function solve(input) {
let temp = input[0].split(' ') // => ['3', '*', '4']
let a = Number(temp[0])
let b = Number(temp[2])
if (temp[1] === '+') {
console.log(a + b)
} else if (temp[1] === '-') {
console.log(a - b)
} else if (temp[1] === '*') {
console.log(a * b)
} else { // 不是加減乘,就是除
console.log(a / b)
}
}
```
### [LIOJ 1030:判斷迴文](https://oj.lidemy.com/problem/1030)
```js
// 反過來輸出
function reverse(str) {
let result = '';
for (let i = str.length - 1; i >= 0; i -= 1) {
result += str[i];
}
return result;
}
// 前面已宣告過 lines,這裡改用 input 作為參數,拿取內容 lines
function solve(input) {
const str = input[0];
if (reverse(str) === str) {
console.log('True');
} else {
console.log('False');
}
}
```
### [LIOJ 1031:完全平方和](https://oj.lidemy.com/problem/1031)
解一:找出比 N 小的完全平方數
```js
function square(n) {
let result = 0
for (let i = 1; i*i <= n; i += 1) {
result += i*i
}
return result
}
console.log(square(30))
// 印出 55,也就是 1+4+9+16+25 = 55
```
解二:判斷是否為完全平方數
```js
function isSquare(n) {
let r = Math.floor(Math.sqrt(n))
return r*r === n
}
// 前面已宣告過 lines,這裡改用 input 作為參數,拿取內容 lines
function solve(input) {
const n = Number(input[0]);
let sum = 0
for (let i = 1; i <= n; i += 1) {
if (isSquare(i)) {
sum += i
}
}
console.log(sum);
}
```
### [LIOJ 1032:平面距離計算](https://oj.lidemy.com/problem/1032)
```js
function distance(x1, y1, x2, y2) {
let dis = Math.sqrt(
abs(x1 - x2) * abs(x1 - x2) +
abs(y1 - y2) * abs(y1 - y2)
)
return dis.toFixed(2) // 取到小數點第二位
}
// 回傳絕對值
function abs(n) {
if (n < 0) {
return -n
}
return n
}
function solve(input) {
let t = Number(input[0])
for (let i = 1; i <= t; i += 1) {
let start = (i - 1)*4 + 1
let x1 = Number(input[start])
let y1 = Number(input[start + 1])
let x2 = Number(input[start + 2])
let y2 = Number(input[start + 3])
console.log(distance(x1, y1, x2, y2))
}
}
```
### [LIOJ 1033:最近點對](https://oj.lidemy.com/problem/1033)
```js
// 利用平面距離計算得到的函式
function distance(x1, y1, x2, y2) {
let dis = Math.sqrt(
abs(x1 - x2) * abs(x1 - x2) +
abs(y1 - y2) * abs(y1 - y2)
)
return dis // 取到小數點第二位
}
function abs(n) {
if (n < 0) {
return -n
}
return n
}
function solve(input) {
let n = Number(input[0])
let dots = []
for (let i = 1; i < input.length; i += 1) {
let temp = input[i].split(' ')
dots.push({
x: Number(temp[0]),
y: Number(temp[1])
})
}
let min = Infinity // 最短距離先設一個最大值
let ans = null
for (let i = 0; i < dots.length; i += 1) {
for (let j = i + 1; j < dots.length; j += 1) {
let dis = distance(
dots[i].x, dots[i].y, dots[j].x, dots[j].y
)
if (dis < min) {
min = dis
ans = {
x1: dots[i].x,
y1: dots[i].y,
x2: dots[j].x,
y2: dots[j].y
}
}
}
}
if (ans.x1 > ans.x2) {
console.log(ans.x2 + ' ' + ans.y2)
console.log(ans.x1 + ' ' + ans.y1)
} else if (ans.x1 < ans.x2) {
console.log(ans.x1 + ' ' + ans.y1)
console.log(ans.x2 + ' ' + ans.y2)
} else {
if (ans.y1 > ans. y2) {
console.log(ans.x2 + ' ' + ans.y2)
console.log(ans.x1 + ' ' + ans.y1)
} else {
console.log(ans.x1 + ' ' + ans.y1)
console.log(ans.x2 + ' ' + ans.y2)
}
}
}
```
### [LIOJ 1034:凱薩加密](https://oj.lidemy.com/problem/1034)
```js
// 把字母右移 n 位
function ceaserCipher(n, s) {
// 'a'.charCodeAt(0) = 97
// 減去 97,可得到數字 0 ~ 25
let code = s.charCodeAt() - 97
// 英文字母有 26 個,當數字超過時,對編碼除 26 取餘數
let newCode = (code + n) % 26
// 取得真正的編碼
return String.fromCharCode(newCode + 97)
}
function solve(input) {
let n = Number(input[0])
let str = input[1]
let result = ''
for (let i = 0; i < str.length; i += 1) {
result += ceaserCipher(n, str[i])
}
console.log(result)
}
```
### [LIOJ 1046:圈圈叉叉](https://oj.lidemy.com/problem/1035)
輸入範例:
```
XXO
OXX
XOO
```
利用二維陣列的概念,可知:
```js
input[0] = 'XXO' // 即 (0, i)
input[0][0] = 'X' // 即 (0, 0)
```
```js
function whoWin(input){
// 判斷橫的三排
for (let i = 0; i < 3; i += 1 ) {
if (input[i][0] === input[i][1] && input[i][1] === input[i][2]) {
return input[i][0]
}
}
// 判斷直的三排
for (let i = 0; i < 3; i += 1) {
if (input[0][i] === input[1][i] && input[1][i] === input[2][i]) {
return input[0][i]
}
}
// 判斷斜對角
if (input[0][0] === input[1][1] && input[1][1] === input[2][2]) {
return input[0][0]
}
if (input[0][2] === input[1][1] && input[1][1] === input[2][0]) {
return input[1][1]
}
return 'DRAW'
}
```
### 作業 - [LIOJ 1004:聯誼順序比大小](https://oj.lidemy.com/problem/1004)
```js
// 判斷比大小
function compare(a, b, k) {
if (a === b) {
return 'DRAW';
}
// 若比小,就將 a b 互換,注意 k 是字串不能用 ===
if (k == -1) {
const temp = a;
a = b;
b = temp;
}
// 判斷字串長度是否相同
// 不同:比較字串長度
if (a.length > b.length) {
return 'A';
}
if (a.length < b.length) {
return 'B';
}
// 相同:從字串最左邊開始比大小
if (a.length === b.length) {
for (let j = 0; j < a.length; j += 1) {
// 若相等,就繼續迴圈比下一位
if (a[j] === b[j]) {
continue;
}
if (a[j] > b[j]) {
return 'A';
} else {
return 'B';
}
}
}
}
function solve(input) {
const n = Number(input[0]);
// 長度為 512 個位數以內,不可轉成數字比大小
for (let i = 1; i <= n; i += 1) {
// 輸出會是陣列 => [1, 2, 1]
let [a, b, k] = input[i].split(' ');
console.log(compare(a, b, k));
}
}
```
## 練習實作內建函式
### [LIOJ 1036:Array reverse](https://oj.lidemy.com/problem/1036)
```js
// 反轉陣列
function reverse(arr) {
let result = []
for(let i = arr.length - 1; i >= 0; i -= 1) {
result.push(arr[i]) // 反轉 => [3, 2, 1]
}
return result
}
// 處理輸入
function solve(input) {
let numbers = []
for (let i = 1; i < input.length; i += 1) {
numbers.push(input[i]) // [1, 2, 3]
}
// 傳進 reverse 函式,並印出結果
let arr = reverse(numbers)
for (let i = 0; i < arr.length; i +=1) {
console.log(arr[i])
}
}
```
也可以直接在處理輸入後,直接輸出翻轉後的結果:
```js
// 處理輸入
function solve(input) {
let numbers = []
for (let i = 1; i < input.length; i += 1) {
numbers.push(input[i]) // [1, 2, 3]
}
for (let i = numbers.length - 1; i >= 0; i -= 1){
console.log(numbers[i]);
}
}
```
### [LIOJ 1037:Array filter](https://oj.lidemy.com/problem/1037)
```js
function filter(arr) {
let result = []
for (let i = 2; i < arr.length; i += 1) {
// 跳過不符合的數字
if (arr[i] == arr[0]) {
continue;
}
result.push(arr[i])
}
return result
}
// 處理輸入
function solve(input) {
let numbers = []
for (let i = 0; i < input.length; i += 1) {
numbers.push(input[i]) // [3, 5, 1, 3 , 3, 2, 8]
}
// 傳進 filter 函式,並印出結果
let arr = filter(numbers)
for (let i = 0; i < arr.length; i +=1) {
console.log(arr[i])
}
}
```
或利用回傳函式 callback 來過濾 target:
```js
function filter(arr, callback) {
let result = []
for (let i = 0; i < arr.length; i += 1) {
// 跳過不符合的數字
if (callback(arr[i]) === true) {
result.push(arr[i])
}
}
return result
}
// 處理輸入
function solve(input) {
// 排除目標
let target = Number(input[0])
let arr = []
for (let i = 2; i < input.length; i += 1) {
arr.push(Number(input[i])) // [1, 3 , 3, 2, 8]
}
// 傳進 filter 函式,留下不等於 target 的值
// 也可寫成 filter(arr, element => element !== target)
let newArr = filter(arr, function(element) {
return element !== target
})
for (let i = 0; i < newArr.length; i +=1) {
console.log(newArr[i])
}
}
```
### [LIOJ 1038:Array indexOf](https://oj.lidemy.com/problem/1038?_ga=2.70826384.1291128142.1593271357-1977277931.1590338596)
```js
// 讀取到一行,先把這一行加進去 lines 陣列,最後再一起處理
rl.on('line', (line) => {
lines.push(line);
});
// LIOJ 1038:Array indexOf
function indexOf(arr, searchElement) {
for (let i = 0; i < arr.length; i += 1) {
if (arr[i] === searchElement) {
return i
}
}
return -1
}
// 處理輸入
function solve(input) {
// 目標數字
let target = Number(input[0])
// 要檢查的數字
let arr = []
for (let i = 2; i < input.length; i += 1) {
arr.push(Number(input[i])) // => [1, 2, 3, 3, 3]
}
console.log(indexOf(arr, target))
}
```
### [LIOJ 1039:Array fill](https://oj.lidemy.com/problem/1039)
```js
// LIOJ 1039:Array fill
function fill(arr, value) {
let result = []
for (let i = 0; i < arr.length; i += 1) {
// 陣列每個元素會被取代成 value,也可寫成 result[i] = value
result.push(value)
}
return result
}
// 處理輸入
function solve(input) {
let target = Number(input[0])
let arr = []
for (let i = 2; i < input.length; i += 1) {
arr.push(Number(input[i])) // => [1, 2, 3]
}
let newArr = fill(arr,target)
for (let i = 0; i < newArr.length; i += 1) {
console.log(newArr[i])
}
}
```
### [LIOJ 1040:Array join](https://oj.lidemy.com/problem/1040)
```js
// LIOJ 1040:Array join
function join(arr, separator) {
let result = '';
for (let i = 0; i < arr.length; i += 1) {
result += arr[i]
if (i < arr.length -1) {
result += separator
}
}
return result
}
// 處理輸入
function solve(input) {
const str = input[0];
let arr = []
for (let i = 2; i < input.length; i += 1) {
arr.push(input[i]) // [1, 2, 3]
}
console.log(join(arr, str));
}
```
或是把字串 `1!2 3` 看成 `1 !2!3`,迴圈內就不用再進行判斷:
```js
function join(arr, separator) {
let result = arr[0];
for (let i = 1; i < arr.length; i += 1) {
result += separator + arr[i]
}
return result
}
```
### [LIOJ 1041:String trim](https://oj.lidemy.com/problem/1041)
```js
```
### [LIOJ 1042:String toLowerCase](toLowerCasehttps://oj.lidemy.com/problem/1042)
```js
// LIOJ 1042:String toLowerCase
function toLowerCase(str) {
let result = ''
for (let i = 0; i < str.length; i += 1) {
if (str[i] >= 'A' && str[i] <= 'Z') {
let code = str.charCodeAt(i)
result += String.fromCharCode(code + 32)
} else {
result += str[i] // 若不是大寫就直接加到字串
}
}
return result
}
// 處理輸入
function solve(input) {
console.log(toLowerCase(input[0]))
}
```
### [LIOJ 1043:String endsWith](https://oj.lidemy.com/problem/1043)
```js
```
### [LIOJ 1044:String padEnd](https://oj.lidemy.com/problem/1044)
```js
// LIOJ 1044:String padEnd
function padEnd(str, targetLength, padString) {
if (str.lenght >= targetLength) {
return str
}
// 從 'S 字串長度 + 1' 開始填充,直到預期字串長度
while (str.length < targetLength) {
for (let i = 0; i < padString.length; i += 1) {
str += padString[i]
// 若 S 達到預期字串長度,則結束迴圈
if (str.length === targetLength) {
return str
}
}
}
}
// 處理輸入
function solve(input) {
let S = input[0]
let target = Number(input[1]) // 預期字串的最後長度
let padString = input[2] // 要填充的字串
console.log(padEnd(S, target, padString))
}
```
### [LIOJ 1045:String slice](https://oj.lidemy.com/problem/1045)
```js
// LIOJ 1045:String slice
function slice(str, beginIndex, endIndex) {
let result = ''
for (let i = beginIndex; i < endIndex; i += 1) {
result += str[i]
}
return result
}
// 處理輸入
function solve(input) {
let str = input[0]
let start = Number(input[1])
let end = Number(input[2])
console.log(slice(str, start, end));
}
```
---
## 補充:[Lidemy OJ](https://www.youtube.com/watch?v=v7zv1ixaO3M) 解題方式
有三種方式可以驗證輸出:
1. 直接在 Command Line 介面直接執行 `node index.js`,即可直接輸入數字,按 ctrl+D 輸出結果(Windows 無法執行)
2. 直接在 `index.js` 呼叫 function
3. 新增檔案來放輸入,利用指令來產生輸出:`cat input.txt | node index.js`
補充:Windows 需改成 `cat input.txt | env node code.js` 或是 `cat input.txt | command node code.js`
固定輸入程式碼如下,答題時只需更改 function solve(lines):
```js
var readline = require('readline');
var rl = readline.createInterface({
input: process.stdin
});
var lines = []
// 讀取到一行,先把這一行加進去 lines 陣列,最後再一起處理
rl.on('line', function (line) {
lines.push(line)
});
// 輸入結束,開始針對 lines 做處理
rl.on('close', function () {
solve(lines)
})
// 拿到所有資料
function solve(lines) {
}
```
以[靈魂伴侶](https://oj.lidemy.com/problem/1010)為例:
```
var readline = require('readline');
var rl = readline.createInterface({
input: process.stdin
});
var lines = []
// 讀取到一行,先把這一行加進去 lines 陣列,最後再一起處理
rl.on('line', function (line) {
lines.push(line)
});
// 輸入結束,開始針對 lines 做處理
rl.on('close', function () {
solve(lines)
})
function solve(lines) {
var arr = lines[0].split(' ') // 注意需要取陣列!
var a = Number(arr[0])
var b = Number(arr[1])
if (a === b) {
console.log('Yes')
} else {
console.log('No')
}
}
solve([ // 輸入是陣列,可用函式驗證是否正確
'15 15'
])
```
---
## 補充:使用 eslint 檢查語法
> 相關筆記:[[week3] 設定 eslint:用來檢查語法的工具](https://hackmd.io/m95fj9SsRLe1XDZEdsyBww)
需注意宣告方式、縮排問題、結尾加分號等問題,修改後模板如下:
```
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
});
const lines = [];
// 讀取到一行,先把這一行加進去 lines 陣列,最後再一起處理
rl.on('line', (line) => {
lines.push(line);
});
// 前面已宣告過 lines,這裡改用 input 作為參數,拿取內容 lines
function solve(input) {
"在這裡輸入函式內容"
}
// 輸入結束,開始針對 lines 做處理
rl.on('close', () => {
solve(lines);
});
```