原創(chuàng) 算法解析:撥號(hào)盤走格問題 (Swift解法)

記錄一個(gè)之前遇到的算法題,挺有意思的 希望可以幫到他人
打開手機(jī)撥號(hào)盤。顯示如下圖片。


image.png

給定圖中任意一個(gè)數(shù)字D都可以作為起始點(diǎn)。每次可以朝上下左右任意方向移動(dòng)一格,不可斜著移動(dòng),每次移動(dòng)N步,記錄可能移動(dòng)的組合。
例如D=1 N=3 則可能的組合為[121,123,125,141,145,147]。
例如D=5 N=1 則可能的組合為[5]。

問題的難點(diǎn)在于:
最終組合數(shù)組的個(gè)數(shù)會(huì)隨著N的增加而會(huì)呈幾何增長,因?yàn)楫?dāng)每走一步后,上下左右的可能性就完全不同,需要避免已經(jīng)走過的線路。
走N步也間接表明了最后每個(gè)組合將是一個(gè)N位數(shù),
且當(dāng)無論是起始數(shù)字還是半路上走到的數(shù)字除5和8以外都有限制,例如1無法往上、左方向走,9無法往下、右方向走,0只能往上走。

所以我的思路是:先犧牲一點(diǎn)空間復(fù)雜度,生成一個(gè)數(shù)字為key,相鄰可能性為value的Dictonary對(duì)象。即1的走向是[2,4],2的走向是[1,5,3],3的走向是[2,6] 以此類推。當(dāng)拿到初始數(shù)字時(shí)根據(jù)key進(jìn)行可能性的尋跡,用遞歸的方式深入每一位數(shù)并生成路徑上的組合,應(yīng)該就可以實(shí)現(xiàn)了。

//創(chuàng)建一個(gè)形似撥號(hào)盤的二維數(shù)組
let keyPad = [[1, 2, 3],[4, 5, 6],[7, 8, 9],[-1,0,-1]]

//Create Neighbors of every Number
func createNeighborDic() ->Dictionary<Int,Array<Int>> {
    
    var dic = Dictionary<Int,Array<Int>>.init()
    
    for i in 0...keyPad.count-1 {
        for j in 0...keyPad[i].count-1 {
            let key = keyPad[i][j]
            if key != -1 {
                dic[key] = getNeighbor(target: key, dX: i, dY: j)
            }
        }
    }
    return dic
}

//Forward up down left right get number nearby
func getNeighbor(target:Int,dX:Int,dY:Int) -> [Int] {
    var neighbors:[Int] = []
    
    if (dX - 1) >= 0 {
        //up
        let next = keyPad[dX - 1][dY]
        if next != -1 {
            neighbors.append(next)
        }
    }
    
    if (dX + 1) <= 3 {
        //down
        let next = keyPad[dX + 1][dY]
        if next != -1 {
            neighbors.append(next)
        }
    }
    
    if (dY - 1) >= 0 {
        //left
        let next = keyPad[dX][dY - 1]
        if next != -1 {
            neighbors.append(next)
        }
    }
    
    if (dY + 1) <= 2 {
        //right
        let next = keyPad[dX][dY + 1]
        if next != -1 {
            neighbors.append(next)
        }
    }
    
    return neighbors
}

準(zhǔn)備工作到此結(jié)束,有了createNeighborDic方法返回的Dictionary對(duì)象,后面尋跡的方法就相對(duì)好做一點(diǎn)了。

func append(array:[Int],dic:Dictionary<Int,Array<Int>>,n:Int,num:Int) -> Void {
    var tempNum = num
    //Deep
    var tempN = n
    if tempN > 0 {
        for item in array {
            tempNum = tempNum + item * Int(pow(10.0, Double(n-2)))
            
            if tempN > 2 {
                //If still not deep enough. Continue geting the deep Neighbors
                tempN = tempN - 1
                let tempArray = dic[item]!
                
                append(array: tempArray, dic: dic, n: tempN, num: tempNum)
                //Reset temp value
                tempNum = num
                tempN = n
                
            } else {
                resultArray.append(tempNum)
                //After Got Result, Reset temp value
                tempNum = tempNum - item
            }
        }
    }
}

解釋下append方法里每個(gè)參數(shù):
array:起始的周邊組合,第一次運(yùn)行時(shí)直接給進(jìn)D的組合,例如當(dāng)D=1是給[2,4], D=6時(shí)給[3,5,9], D=8時(shí)給[5,7,0,9]。這些在createNeighborDic方法返回的字典中都可以取到。

dic:通過createNeighborDic獲取來的每個(gè)數(shù)字組合的字典,傳進(jìn)去供每個(gè)途徑的數(shù)字獲取相應(yīng)的數(shù)字組合。

n: 即步數(shù),在append方法每運(yùn)行一次后進(jìn)行一次減1。當(dāng)減到小于2時(shí)(因?yàn)榈谝徊接墒止べx值進(jìn)去)認(rèn)為走滿了N步,保存途徑的數(shù)字。當(dāng)n大于2時(shí),說明還需要繼續(xù)走步,則遞歸調(diào)用自身方法。

tempNum: 每個(gè)組合的數(shù)字集合,此處使用了一個(gè)系統(tǒng)函數(shù)pow(a,b),即a的b次方。因?yàn)樽罱K每個(gè)組合的呈現(xiàn)是以十進(jìn)制數(shù)字的方式。在每一次的走步過程中需要進(jìn)行每個(gè)數(shù)字格的n-2次方計(jì)算。

最后這個(gè)getNeighborByLenght就是主函數(shù),接收d和n參數(shù)并調(diào)用append方法進(jìn)行數(shù)字格的走步和記錄。

func getNeighborByLenght(d:Int,n:Int) -> [Int] {
    print("d = \(d)  n = \(n)")
    if n == 1 {
        return [d]
    }
    
    //Got Dictionary with all number neighbors
    let dic:Dictionary<Int,Array<Int>> = createNeighborDic()
    
    //Got Neighbors of d
    let first = dic[d]
    
    //Init First Number
    let initNumber = Int(pow(10.0,Double(n - 1))) * d
    
    //Append Left Number
    append(array: first!, dic: dic, n: n, num: initNumber)
    return resultArray
}

代碼運(yùn)行結(jié)果:


image.png

對(duì)比結(jié)果和要求基本上符合。但該解法也有可以改進(jìn)的地方。
1.利用了全局變量resultArray進(jìn)行存每次生成的組合,最好是能放在主函數(shù)內(nèi)部。這樣運(yùn)行時(shí)可以不依賴到函數(shù)所在的方法實(shí)體。并且每次運(yùn)行不需要做清空操作。但由于使用了遞歸的做法,每次都去傳遞這個(gè)數(shù)組對(duì)象感覺也不太好。

2.append方法中的形參需要在每次運(yùn)行中進(jìn)行變化,這里采用的方式是用var對(duì)象接收,但這樣只是一個(gè)權(quán)益之計(jì),可能會(huì)導(dǎo)致可變參數(shù)和不可變參數(shù)之間的界線模糊。應(yīng)該選用let 和 var區(qū)分開哪些是可變哪些是不可變。

3.使用Dictionary存儲(chǔ)了數(shù)字格走步的可能性犧牲了空間復(fù)雜度,算式有些投機(jī)取巧,當(dāng)撥號(hào)盤不再局限于0~9而是成千上萬后,這個(gè)方式的劣勢就出現(xiàn)了。按道理應(yīng)該應(yīng)用一些A* D*之類尋路算法的思路去解決,后續(xù)有時(shí)間了再深入研究下。

最近略忙,過段時(shí)間優(yōu)化了再貼優(yōu)化后的代碼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容