不久之前在codewars上發現一道關於數數的題,當時沒有做出來,然後過了兩天翻juejin,看見了一篇講簡單的動態規劃演算法的文章,突然給了我一點靈感,然後就嘗試著用這種辦法解決掉了這個問題。話不多說,來看一下這道題:
這裡有一張圖片,但是不知道為什麼不能顯示,作為一個前端,簡直不能忍,憤然開啟控制檯。。。 然後。。。決定還是找一張圖吧。 大概解釋一下這個題。 你可能已經很熟悉將幾何手勢鎖屏作為智慧手機的安全防範措施,為了開啟手機,你需要一直在螢幕上滑動手指將螢幕上中網格的點連線起來繪製成正確的圖案,下面這張圖片是7個點的例子(然而並沒有圖。。。)。這道題,你要做的是完成這個函式,這個函式要傳入兩個引數,一個是對應網格的其中一個字元,一個是需要連線的網格上點的個數,該函式必須返回從給定點開始的並且連線次數是給定長度的所有情況的數量。注意這裡連線點只能用直線:
·水平的(類似A-B)
·垂直的(類似D-G)
·對角線(類似B-I或者I-E)
·經過已經使用的點(類似G-C經過E)
測試的例子包含一些組合數量的示例,以幫助您檢查程式碼。
額外選項:
你可能很好奇,對於安卓的鎖屏手勢,有效的必須是4到9個點,並且總共有389112個可能的有效組合。
好,現在應該差不多明白這個題了,其實就是以給的點開始然後根據給的數量按照規則去連線,最後返回一共多少種情況。我們先分析一下這道題,首先,我們要找到他的引數是什麼,我們就以給出的測試的例子為例, 第一個引數是D,第二個引數是3,我們看見了D最先就要知道他在圖中的位置, 所以我們先建立一個9宮格
var originDots = [
['A','B','C'],
['D','E','F'],
['G','H','I'],
]
複製程式碼
這樣我們就能看出哪些點能夠連線起來,然後還要知道的是哪些點已經被連線了,所以我們要建立一個和這個一樣的網格,來表示哪些點用過,哪些點沒用過,
var checkedArr = [
[0,0,0],
[0,0,0],
[0,0,0]
]
複製程式碼
我們用這個來表示初始化的狀態,然後根據給的點將該點的位置變為已使用,
var lo = []
originDots.forEach((item, index)=>{
item.forEach((itemA, indexA) =>{
if (itemA === firstDot) {
lo.push(index)
lo.push(indexA)
}
})
})
// var checkedArr = [
// [0,0,0],
// [1,0,0],
// [0,0,0]
// ]
複製程式碼
類似於這樣,接下來我們就需要知道,哪些點能夠作為滿足條件的下一環被連起來,這個時候我們就需要記錄一下當前的這個點的位置,以及連線這個位置所需要的滿足條件。
var lastX = 0;
var lastY = 0;
function canCheck(x, y){
//同一行
if(x == lastX && Math.abs(y-lastY) > 1){
if(checkedArr[x][1] == 0){
return false;
}
}
//同一列
if(y == lastY && Math.abs(x-lastX) > 1){
if(checkedArr[1][y] == 0){
return false;
}
}
//對角
if((x+lastX)/2==1 && (y+lastY)/2==1 && x != lastX){
if(checkedArr[1][1] == 0){
return false;
}
}
return true;
}
複製程式碼
好,接下來要做的就是尋找滿足條件的點,所以我們就要遍歷我們的網格,只要是沒有被使用過的就可以成為備選,然後記得我們的次數,不要選滿網格,
function search(startX,startY,len){
//判斷是否可用
if(!canCheck(startX,startY)){
return 0;
}
//選擇狀態
checkedArr[startX][startY] = 1;
//找到了一個
if(len == 1){
//恢復未選擇狀態
checkedArr[startX][startY] = 0;
return 1;
}
if (len > 9) {
return 0
}
var count = 0;
for(var x=0;x<=2;x++){
for(var y=0;y<=2;y++){
if(checkedArr[x][y] == 0){
lastX = startX;
lastY = startY;
count += search(x,y,len - 1);
}
}
}
checkedArr[startX][startY] = 0;
return count;
}
複製程式碼
ok,這個地方要注意的一下就是checkedArr[startX][startY] = 0 ,這塊可以都認為是假設,所以在每次嘗試之後要記得重置一下狀態。最後,完整程式碼
var originDots = [
['A','B','C'],
['D','E','F'],
['G','H','I'],
]
var checkedArr = [
[0,0,0],
[0,0,0],
[0,0,0]
]
var lastX = 0;
var lastY = 0;
function countPatternsFrom(firstDot, length) {
// Your code here
var lo = []
originDots.forEach((item, index)=>{
item.forEach((itemA, indexA) =>{
if (itemA === firstDot) {
lo.push(index)
lo.push(indexA)
}
})
})
return exe(lo[0],lo[1],length)
}
function exe(x,y,len){
init(x,y);
return search(x,y,len);
}
function search(startX,startY,len){
//判斷是否可用
if(!canCheck(startX,startY)){
return 0;
}
//選擇狀態
checkedArr[startX][startY] = 1;
//找到了一個
if(len == 1){
//恢復未選擇狀態
checkedArr[startX][startY] = 0;
return 1;
}
if (len > 9) {
return 0
}
var count = 0;
for(var x=0;x<=2;x++){
for(var y=0;y<=2;y++){
if(checkedArr[x][y] == 0){
lastX = startX;
lastY = startY;
count += search(x,y,len - 1);
}
}
}
checkedArr[startX][startY] = 0;
return count;
}
function canCheck(x, y){
//同一行
if(x == lastX && Math.abs(y-lastY) > 1){
if(checkedArr[x][1] == 0){
return false;
}
}
//同一列
if(y == lastY && Math.abs(x-lastX) > 1){
if(checkedArr[1][y] == 0){
return false;
}
}
//對角
if((x+lastX)/2==1 && (y+lastY)/2==1 && x != lastX){
if(checkedArr[1][1] == 0){
return false;
}
}
return true;
}
function init(x,y){
checkedArr = [
[0,0,0],
[0,0,0],
[0,0,0]
];
lastX = x;
lastY = y;
}
console.log(countPatternsFrom('D', 3)); // 37
複製程式碼
整個程式碼沒什麼亮點,隨便一個人都能寫出來,不過最重要的應該是這個思路,一開始看到這個題的時候,我的思維一直都停在用乘法來解決這個問題,就像數學的排列組合那樣,所以根本就想不到可以用加法來解決這件事,最後要感謝一下juejin.im/post/5a29d5… 這篇文章,如果不是看了這個,我不知道我什麼時候才能突破之前的思維模式,所以大家一定要多讀書,多看報。。。