題
列印由星號組成的x層三角形,如下圖:
解
看規律, 設三角層數為x,每層星星數為y,每層開頭空格數為z
那麼,對於第i層:
- y 為1,3,5,7,9...奇數。光知道奇數你並不知道印象中的公式是2n-1還是2n+1更哪個更適合這裡, 那我們一步步來, 這裡隔離變與不變, 1是最開始的基準, 3和1的關係是+2, 5和3的關係是+2... 因此每次加2,即1, 1+2, 1+2+2, 1+2+2+2...注意這裡隔離變與不變, 數量不變的1, 和數量變化的2, 那麼就看這個變化的2的數量和i的關係, 即0, 1, 2, 3...和1, 2, 3...的關係,這樣才由題意得y = 1+(i-1)*2 = 2i-1;
- z 在最後一層為0,朝上依次增加1,共有x層,故最上層為x-1,第二層x-2, 第三層x-3, 因此z = x-i;
雖然很多時候這兩個簡單的變數之間的數量關係可以一眼看出, 但通常都是碰運氣的不斷嘗試拼湊, 本著九年義務教育提供的數學素養, 我們應該可以從頭推出這些式子, 並且有理有據, 而不是背下來.
個人最討厭背了,除了3.1415926, 九九乘法表之類的, 爸媽的手機號... 其他時候是能不背就不背.
我們如果能掌握如何產生公式, 是不是就比直接背公式更容易記憶呢?短期看背的收益是高回報的, 無需思考, 提取就可以. 而理解就像計算機中的函式, 他們多了之後可以互相利用, 組合成更復雜的功能. 而背除了詩詞能假以時日身臨其境,有所感慨可以陶冶情操外, 很多時候他們真的除了提取資料, 毫無發展和其他有趣的事情聯絡起來從而有更大的用處.
當然理解是要付出代價的, 即理解的時間, 和從原資料推演計算的時間, 因此重要的或者有趣的才要去理解, 如果是一次性使用的資訊, 比如學校旁的小麵館說13號擔擔麵好了~13號在哪~, 這樣的資訊還是直接背下來比較好, 當然你也可以想象背叛上帝的猶大, 藉此把13記下來
而手機驗證碼的六位, 原先還要不斷重複口唸450893, 生怕有人跟我說話我又要切換應用重新記, 哈利路亞, 這種純粹沒有邏輯的死板的資訊, 現在的輸入法自帶的資訊提示, 不用切換到簡訊, 直接點一下就Ok了.
看, 越是死的東西, 越是會被機器+軟體替換掉.
簡易程式碼實現
const printTriangle = x => {
for(let i=1; i<=x; i++) {
console.log(" ".repeat(x-i)+ "*".repeat(2*i-1))
}
}
printTriangle(7)
複製程式碼
效果:
VM189:3 *
VM189:3 ***
VM189:3 *****
VM189:3 *******
VM189:3 *********
VM189:3 ***********
VM189:3 *************
複製程式碼
遐想
程式碼邏輯很簡單,這裡有一點引起我的遐想。
以*.repeat()
為例,如果
- 需要1個
*
,是我們程式設計師手動輸入的,無法精簡 - 需要2個
*
,可能我們更願意手動輸入,也無需精簡 - 需要3個
*
,可能我們會想相關重複字元的API是不是比三個星星簡單,於是想到str.repeat()
, 但很明顯,還是手動輸入***
更簡單 - 需要4,5,6,7個
*
呢?很明顯,這時候我們理性一點,在這種事上不能夠選擇相信我們的眼睛真的每次都能數對數目,儘管小學算術經常拿滿分 :) - 而如果我們不是一次只需要幾個
*
,而是同時需要1個,2個,3個,4個*
呢?這時候我們會選擇自動生成。即有str.repeat()
那就用它,沒有的話呢?
手動實現str.repeat()
第一步,先保證基本能用最簡單的
const repeat = (str,n) => {
let result = "";
for(let i=0; i<n; i++) {
result = result + str
}
return result
}
複製程式碼
複雜度:O(n)
第二步: 優化
上面的實現間接生成了任意長度的*
字串
*
, 手動輸入**
, 由*
+*
***
, 由**
+*
****
, 由***
+*
*****
, 由****
+*
******
, 由*****
+*
- ...
而對於本題目需要奇數個長度的*
, 這裡生成的一半都是不需要的, 造成了浪費
那麼怎麼才能不浪費呢.
*
, **
, ****
, ********
, 每次以之前的字元為基本, 自我複製,達到2倍法來逼近指定長度, 達到log2(n)的複雜度(想一想二分法...)
但是對於本題而言, *
, **
, ****
, ********
, ****************
是相對不夠的, 所以
- 對於每行的
*
, 從第一層的*
開始, 不斷的在上一層字串 +**
, - 而對於每行的空格
" "
, 一開始就有x-1個, 所以要最大速度的增加到x-1個字串, 用2倍法, O(log2(n))複雜度, 2^40 = 10995,1162,7776, 即長度為一萬億以下的字串的重複, 不超過40次計算即可拼接成.
const printTriangle = x => {
let blanks_max_length = x-1
let blanks_max = "" // empty str
let blank = " " // blank str
for(;blanks_max_length!=0;){ // only stop while length became 0,
// there is no (let i i...i++), because it's all about length!
if(blanks_max_length%2===1) { // remember decimal 5 trans into binary 101?
// 5/2 = 2...1, 2/2 = 1...0, 1/2 = 0...1,
// the first remainder 1 indicates 1 while the last 1 indicate 2^2 which is 4
blanks_max = blanks_max + blank // so here if there remains 1, blanks_max should add 1 blank
}
blanks_max_length = Math.floor(blanks_max_length/2) // here is the result of /2
blank = blank + blank // here is where log2(n) come from, cause we double the string rather than linear increase
}
let star = "*"
const addon = "**"
// first line
console.log(blanks_max + star)
// other lines
for(let i=2; i<x; i++) {
star = star + addon
console.log(blanks_max.slice(0, x-i) + star)
}
// last line
console.log(star+addon)
}
複製程式碼
- 對比結果時, 我發現第一行和其他行的格式縮排不一致, 找了半天程式碼, 我預設其他是對的, 那麼一對比結果可能正確的那個就表現的錯誤, 而我怎麼也找不到這錯在哪, 這個時候要聯絡上下文, 我發現其實這只是因為兩個邏輯, 第一個邏輯產生了1個例項即第一行, 第二個邏輯產生了更多的例項即其他行, 當錯誤的那個邏輯應用在很多行, 導致其他行一致性的表現規律, 以至於你認為其他行都格式正確了, 第一行是錯誤的, 其實這不是多對一, 本質上這是一對一, 如果不是這個邏輯的這一行錯了, 就是那個邏輯的很多行錯了!
- 為什麼會這麼用除2判斷餘數來標識32,16,8421碼的位是否存在的方式來組合總空格的長度以及用空格double的方式實現複雜度O(log2(n)), 首先考慮的是先生成第一層最長的空格, 然後依次遞減. 其實這是個str.repeat()從O(n)到O(log2(n))的方法,被強行套在這裡.
- 關於本題實際上更適合生成空字串逐個加1長度, 存入棧, 然後列印的時候出棧, 滿足倒序列印的特點
程式碼如下
const printTriangle = x => {
let blanks_max_length = x-1
let blanks_arr = []
let blank_char = " "
let tmp =""
for (let i=0; i<blanks_max_length; i++) {
tmp = tmp + blank_char
blanks_arr.push(tmp)
}
let star = "*"
const addon = "**"
// first line
console.log(blanks_arr.pop() + star)
// other lines
for(let i=2; i<x; i++) {
star = star + addon
console.log(blanks_arr.pop() + star)
}
// last line
console.log(star+addon)
}
複製程式碼
噢, 醜陋的程式碼, 就是為了不產生浪費的字串.
好了, 上面給出了一些個人答案,這些答案分別面向節省程式設計師時間(str.repeat)、面向自己實現str.repeat(),面向不浪費生成的物件等角度。
最後還是好愛第一個答案,不禁讓我想到一句話
JavaScript是世界上最好的語言~