1. 程式人生 > 實用技巧 >JS寫出計算24點演算法

JS寫出計算24點演算法

前言

  休息的時候無意間看到群裡有人發出了華為的校招題,一開始看題目的時候覺得很簡單,於是晚上就試著寫了一下,結果寫的過程中打臉,不斷的整理邏輯不斷的重寫,但我的性格又是不做出來晚上睡不好的那種,於是在做出來的時候就分享給大家(快凌晨三點了有木有,這校招題難度都達到這級別了?o(╥﹏╥)o)

題目描述

  

  審題要注意:1+2+3*4是前面三個已經相加為6再乘4,沒有括號!!

程式碼:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta 
name="viewport" content="width=device-width, initial-scale=1.0"> <title>21點</title> <script> // 牌和對應的權重 const pokerBox = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"];//下標+1剛好就是對應的分值 let calcSym = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];//0,1,2 3,4 5,6,7 8,9分別對應+-*/
function Calculate(a, b, c) { if (c <= 2) return a + b; if (c <= 4) return a - b; if (c <= 7) return a * b; if (c <= 9) return a / b; return -1; } function filter(c) { if (c <= 2) return "+"; if (c <= 4) return
"-"; if (c <= 7) return "*"; if (c <= 9) return "/"; return; } let answer = "NONE";//回覆的字串 預設回覆NONE,表示無解 function Calculate24(a, b, c, d, C1, C2, C3) { let sum = Calculate(Calculate(Calculate(a, b, C1), c, C2), d, C3); if (sum === 24) answer = `公式為:${a} ${filter(C1)} ${b} ${filter(C2)} ${c} ${filter(C3)} ${d} = ${sum}`; return sum; } // 全排列 //這裡的全排序就是把原先的陣列複製一個出來,然後新陣列代替原先陣列刪除該值,temp陣列新增該值,當新陣列的長度為0,說明轉移完成,就把temp陣列放入matrix陣列中 function permutation(pokers) { let matrix = []; const subFunc = (arr, temp) => { if (temp.length > 4) temp.length = 4;//為了避免過長 if (arr.length === 0) matrix.push(temp); arr.forEach((elem, i) => { subFunc([...arr.slice(0, i), ...arr.slice(i + 1)], [...temp, elem]); }); } subFunc(pokers, []); return matrix; }; // 計算總數為24 function Count24(a, b, c, d) { calcSym.sort((x, y) => x - y);//升序排序 if (Calculate24(a, b, c, d, calcSym[0], calcSym[1], calcSym[2]) === 24) return true;//第一次判斷如果符合就不需要執行下面的迴圈了 let i = 1;//上面判斷了一次,因此這裡從1開始 if (calcSym.length <= 10) calcSym = [...new Set(permutation(calcSym).flatMap(item=>item.join()))].map(item=>item.split(","));//二維陣列去重,並獲取全排的陣列(即每一種可能性) while (true) { if (Calculate24(a, b, c, d, calcSym[i][0], calcSym[i][1], calcSym[i][2]) === 24) return true; if (i < calcSym.length - 1) i++; else return false;//如果陣列遍歷完都沒 }; return false; } function init() { if (calcSym.length === 12) calcSym = permutation(calcSym);//獲取全排的陣列(即每一種可能性) } init();//初始化就立即執行 // 對輸入的數字進行一次全排 function calcNumber(arr) { if (Count24(arr[0], arr[1], arr[2], arr[3])) return true;//這一步滿足那麼下面就不用執行permutation了,因為底層是遞迴,很消耗效能 let i = 1; if (arr.length <= 4) arr = [...new Set(permutation(arr).flatMap(item=>item.join()))].map(item=>item.split(","));//二維陣列去重 if (arr.length > 1) { while (true) { if (Count24(arr[i][0], arr[i][1], arr[i][2], arr[i][3])) return true; if (i < arr.length - 1) i++; else return answer = "NONE"; } }; return answer = "NONE"; } // 當我輸入完游標離開的時候就開始判斷並計算 function pokers(event) { let arr = event.value.trim().split(" "); if (arr.length > 4) { arr.length = 4; document.getElementById("poker").value = arr.join(' '); alert("您輸入的牌數大於4張,這邊自動幫您刪除"); } if (arr.some(item => !pokerBox.includes(item))) alert("ERROR"); else { let arrNew = arr.map(item => { return pokerBox.indexOf(item) + 1 });//計算權重 calcNumber(arrNew);//執行計算 } } function dialog() { alert(answer) }; </script> </head> <body> <!-- 這裡設定為失去焦點就開始計算是為了儘量減少使用者等待的時間,但注意不要設定為輸入就開始計算,否則瀏覽器會卡到崩潰 --> <!-- 由於是遍歷陣列獲取結果,如果使用者輸入的值不為24,那麼系統會查詢的很慢,這個時候的優化方案有: 一、每次使用者輸入的值和對應的回覆儲存在一個數組內,下次使用者輸入時先判斷是否在該陣列內,不在的時候再執行計算 二、我們可以先排除一部分不可能的值放入陣列,比如使用者輸入2 2 2 2或A A A A,這種怎麼算都不可能為24,如果使用者輸入的為這一類就直接Pass 三、先把最耗時的calcSym陣列的全排改為使用者一進入頁面就先非同步載入計算 --> <input type="text" onblur="pokers(this)" name="21" id="poker"> <input type="button" onclick="dialog()" value="confirm" /> </body> </html>

實現的效果:

總結思路:

  題目第一眼看到就應該想到遞迴,之前我是把加減乘除都設為一個方法,想採用面向切面的方式進行計算,但是這種方式邏輯複雜且無法計算複雜一點的公式,因此就改為直接把所有可能出現的結果都拿出來一一比對,只要其中一個為24就終止迴圈,否則迴圈結束之後返回NONE;

  calcSym=[0,1,2,3,4,5,6,7,8,9];//0,1,23,45,6,78,9分別對應+-*/,這裡為三個+,兩個-,三個*,兩個除,大家可以推理得出,6+6+6+6,1*2*3*4,2*13-1-1,13*13/13+11等等,除號和減號最多隻可能有兩個,而加號和乘號最多可以為三個;

  至於全排列方法permutation,是借鑑了STL的next_permutation函式(C++),之所以二維陣列去重也是封裝的方法可能出現多個數組重複的情況,要知道每多一個數組,底層是用遞迴查詢一遍,瀏覽器會非常卡;

  最後就是我在程式碼中提到的優化方法,有興趣的小夥伴可以去試一下,程式碼還有優化的空間。