原创 算法解析:拨号盘走格问题 (Swift解法)

记录一个之前遇到的算法题,挺有意思的 希望可以帮到他人
打开手机拨号盘。显示如下图片。


image.png

给定图中任意一个数字D都可以作为起始点。每次可以朝上下左右任意方向移动一格,不可斜着移动,每次移动N步,记录可能移动的组合。
例如D=1 N=3 则可能的组合为[121,123,125,141,145,147]。
例如D=5 N=1 则可能的组合为[5]。

问题的难点在于:
最终组合数组的个数会随着N的增加而会呈几何增长,因为当每走一步后,上下左右的可能性就完全不同,需要避免已经走过的线路。
走N步也间接表明了最后每个组合将是一个N位数,
且当无论是起始数字还是半路上走到的数字除5和8以外都有限制,例如1无法往上、左方向走,9无法往下、右方向走,0只能往上走。

所以我的思路是:先牺牲一点空间复杂度,生成一个数字为key,相邻可能性为value的Dictonary对象。即1的走向是[2,4],2的走向是[1,5,3],3的走向是[2,6] 以此类推。当拿到初始数字时根据key进行可能性的寻迹,用递归的方式深入每一位数并生成路径上的组合,应该就可以实现了。

//创建一个形似拨号盘的二维数组
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
}

准备工作到此结束,有了createNeighborDic方法返回的Dictionary对象,后面寻迹的方法就相对好做一点了。

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方法里每个参数:
array:起始的周边组合,第一次运行时直接给进D的组合,例如当D=1是给[2,4], D=6时给[3,5,9], D=8时给[5,7,0,9]。这些在createNeighborDic方法返回的字典中都可以取到。

dic:通过createNeighborDic获取来的每个数字组合的字典,传进去供每个途径的数字获取相应的数字组合。

n: 即步数,在append方法每运行一次后进行一次减1。当减到小于2时(因为第一步由手工赋值进去)认为走满了N步,保存途径的数字。当n大于2时,说明还需要继续走步,则递归调用自身方法。

tempNum: 每个组合的数字集合,此处使用了一个系统函数pow(a,b),即a的b次方。因为最终每个组合的呈现是以十进制数字的方式。在每一次的走步过程中需要进行每个数字格的n-2次方计算。

最后这个getNeighborByLenght就是主函数,接收d和n参数并调用append方法进行数字格的走步和记录。

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
}

代码运行结果:


image.png

对比结果和要求基本上符合。但该解法也有可以改进的地方。
1.利用了全局变量resultArray进行存每次生成的组合,最好是能放在主函数内部。这样运行时可以不依赖到函数所在的方法实体。并且每次运行不需要做清空操作。但由于使用了递归的做法,每次都去传递这个数组对象感觉也不太好。

2.append方法中的形参需要在每次运行中进行变化,这里采用的方式是用var对象接收,但这样只是一个权益之计,可能会导致可变参数和不可变参数之间的界线模糊。应该选用let 和 var区分开哪些是可变哪些是不可变。

3.使用Dictionary存储了数字格走步的可能性牺牲了空间复杂度,算式有些投机取巧,当拨号盘不再局限于0~9而是成千上万后,这个方式的劣势就出现了。按道理应该应用一些A* D*之类寻路算法的思路去解决,后续有时间了再深入研究下。

最近略忙,过段时间优化了再贴优化后的代码。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342