這是來自 LIOJ 上的題目,因為陷阱還蠻特別的,所以想記錄下來。
魔鬼藏在細節裡
我想很多人應該都很困惑明明在自己電腦跑都 OK,但系統還是一直噴 Wrong Answer (´⊙ω⊙`)
我其實也搞了老半天才發現,但在討論陷阱之前,先談談這題的解法。
這題的解法其實不難,就是「根據小偷可以偷的數量,把每樣物品價格由大到小做加總」,所以我是這樣做的:
- 把物品陣列做排序,由大排到小
- 把陣列加總,從第一個加到第 n 個(n = 小偷能偷的總數量)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| function solve(lines) { let totalValue = 0 const max = Number(lines[0]) const items = transformToNumber(lines.slice(2))
items.sort((a, b) => b - a) for(let i=0; i<max; i++) { totalValue += items[i] } console.log(totalValue) }
function transformToNumber(arr) { let result = [] for(let i=0; i<arr.length; i++) { result.push(Number(arr[i])) } return result } solve(['3', '5', '1', '3', '5', '7', '9'])
|
這樣子看起來沒有什麼問題,範例給的測試資料也能得到正確結果。
凡事都要設想周到
但是!有一件很重要的事情被忽略了:如果小偷能偷的數量 >= 所有物品的數量呢?
沒有錯,我想這一題 AC 率會那麼低就是因為很多人跟我一樣都忽略掉了這件事,所以才會懷疑人生老半天。
先讓我們看看如果在「能偷的數量 >= 物品的數量」的情況會發生什麼:
1
| solve(['100', '5', '1', '3', '5', '7', '9'])
|
答案是 NaN
,問題出在這一段:
1 2 3 4
| for(let i=0; i<max; i++) { totalValue += items[i] }
|
陣列的物品只有 [1, 3, 5, 7, 9]
這五個,而 max
是 100,也就是說這個迴圈會從 0 跑到 99。
所以請想想看,在 item[5]
的時候會發生什麼?
1 2
| totalValue += undefined
|
現在你知道為什麼會冒出 NaN
了吧。
正確的作法
經過剛剛的教訓後,我們可以修正成這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| function solve(lines) { let totalValue = 0 const max = Number(lines[0]) const totalItem = Number(lines[1]) const items = transformToNumber(lines.slice(2))
if(max >= totalItem) { for(let i=0; i<items.length; i++) { totalValue += items[i] } console.log(totalValue) } else { items.sort((a, b) => b - a) for(let i=0; i<max; i++) { totalValue += items[i] } console.log(totalValue) } }
function transformToNumber(arr) { let result = [] for(let i=0; i<arr.length; i++) { result.push(Number(arr[i])) } return result } solve(['100', '5', '1', '3', '5', '7', '9'])
|
利用 slice() 寫出更簡潔的作法
除了用 for 迴圈來求陣列總和以外,你也可以換個思維:
- 把物品陣列做排序,由大排到小
- 切割出物品陣列的範圍,從 0 切到 n 的範圍(n = 小偷能偷的總數量)
- 把切割出的陣列加總
所以最後會寫出這樣子的程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| function solve(lines) { let totalValue = 0 const max = Number(lines[0]) const totalItem = Number(lines[1]) const items = transformToNumber(lines.slice(2)) items.sort((a, b) => b - a) const availableItems = items.slice(0, max) totalValue = availableItems.reduce((acc, elem) => acc + elem, 0) console.log(totalValue) }
function transformToNumber(arr) { let result = [] for(let i=0; i<arr.length; i++) { result.push(Number(arr[i])) } return result }
|
之所以可以省略掉判斷的原因是因為這一句:
1
| const availableItems = items.slice(0, max)
|
如果把 slice()
的第二個參數設成比陣列長度大的數字,會自動視為是最後一個元素的意思。因此 max
不管是多大的數字,availableItems
都會是一個完整的陣列。