使用电话键盘生成 10 位数字
给定一个如下所示的手机键盘:
1 2 3
4 5 6
7 8 9
0
从1开始可以组成多少个不同的10位数字?限制是从一位数字到下一位数字的移动类似于国际象棋游戏中马的移动。
例如。如果我们是 1 那么下一个数字可以是 6 或 8 如果我们位于 6,则下一个数字可以是 1、7 或 0。
允许重复数字 - 1616161616 是有效数字。
有没有多项式时间算法可以解决这个问题?该问题要求我们只给出 10 位数字的个数,而不一定列出数字。
编辑:我尝试将其建模为一个图表,每个数字都有 2 或 3 个数字作为其邻居。然后我使用 DFS 导航到 10 个节点的深度,然后每次达到 10 的深度时增加数字的计数。这显然不是多项式时间。假设每个数字只有 2 个邻居,则这至少需要 2^10 次迭代。
这里的变量是位数。我已经采取了例如。 10 位数字。它也可以是 n 位数字。
Given a phone keypad as shown below:
1 2 3
4 5 6
7 8 9
0
How many different 10-digit numbers can be formed starting from 1? The constraint is that the movement from 1 digit to the next is similar to the movement of the Knight in a chess game.
For eg. if we are at 1 then the next digit can be either 6 or 8
if we are at 6 then the next digit can be 1, 7 or 0.
Repetition of digits are allowed - 1616161616 is a valid number.
Is there a polynomial time algorithm which solves this problem? The problem requires us to just give the count of 10-digit numbers and not necessarily list the numbers.
EDIT: I tried modeling this as a graph with each digit having 2 or 3 digits as its neighbors. Then I used DFS to navigate upto the depth of 10 nodes and then increment the count of numbers each time I reached the depth of 10. This obviously is not polynomial time. Assuming each digit had just 2 neighbors, this would have required at least 2^10 iterations.
The variable here is the number of digits. I have taken the eg. of 10 digit numbers. It could as well be n-digits.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(12)
当然可以在多项式时间内完成。这是动态编程或记忆。
在此示例中,我们假设 N(位数)等于 10。
像这样递归地思考:使用从
1
开始的10位数字可以构造多少个数字?答案是
有多少个“从8开始的9位数字” “有吗?嗯,
等等。当您得到问题“从
X
开始有多少个 1 位数字”(答案显然是 1)时,就达到了基本情况。当谈到复杂性时,关键的观察是您重用以前计算的解决方案。例如,回答“有多少个从
3
开始的 5 位数字” 可以在回答“有多少个 6-”时使用。从8
开始的数字”和“从4
开始有多少个6位数字”。这种重用使得复杂性从指数级下降到多项式级。让我们仔细看看动态编程解决方案的复杂性:
这种实现将按以下方式填充矩阵:
该算法只是一次填充一个单元格,并且该矩阵的维度为 10*N,因此以线性时间运行。
我凭空写下来的,如有错别字请指正。
Sure it can be done in polynomial time. It's an excellent exercise in dynamic programming or memoization.
Lets assume N (the number of digits) equals 10 for the example.
Think of it recursively like this: How many numbers can I construct using 10 digits starting from
1
?Answer is
So how many "9-digit numbers starting from 8" are there? Well,
and so on. Base case is reached when you get the question "How many 1-digit numbers are there starting from
X
" (and the answer is obviously 1).When it comes to complexity, the key observation is that you reuse previously computed solutions. That is for instance, the answer to "how many 5-digit numbers starting from
3
" there are, can be used both when answering "how many 6-digit numbers are there starting from8
" AND "how many 6-digit numbers are there starting from4
". This reuse make the complexity collapse from exponential to polynomial.Let's take a closer look at the complexity of a dynamic programming solution:
Such implementation would fill in a matrix in the following way:
The algorithm simply fills the matrix one cell at a time, and the matrix is of dimension 10*N, and thus runs in linear time.
Wrote it down from the top of my head, please correct me if there are any typos.
我决定解决这个问题并使其尽可能可扩展。该解决方案允许您:
定义您自己的棋盘(手机键盘、棋盘等)
定义您自己的棋子(马、车、象等);您必须编写具体的类并从工厂生成它。
通过一些有用的实用方法检索几条信息。
这些类如下:
PadNumber:定义电话垫上按钮的类。可以重命名为“Square”以代表棋盘正方形。
ChessPiece:定义所有棋子字段的抽象类。
运动:定义运动方法并允许工厂生成零件的接口。
PieceFactory:生成棋子的工厂类。
Knight:继承自ChessPiece并实现Movement的具体类
PhoneChess:入口类。
驱动程序:驱动程序代码。
好的,代码如下:)
ChessPiece
移动接口:
PieceFactory
Knight 类
PhoneChess 入口类
Driver 代码:
I decided to tackle this problem and make it as extensible as I can. This solution allows you to:
Define your own board (phone pad, chess board, etc.)
Define your own chess piece (Knight, Rook, Bishop, etc.); you will have to write the concrete class and generate it from the factory.
Retrieve several pieces of information through some useful utility methods.
The classes are as follows:
PadNumber: Class defining a button on the phone pad. Could be renamed to 'Square' to represent a board square.
ChessPiece: Abstract class that defines fields for all chess pieces.
Movement: Interface that defines movement methods and allows for factory generation of pieces.
PieceFactory: Factory class to generate Chess pieces.
Knight: Concrete class that inherits from ChessPiece and implements Movement
PhoneChess: Entrance class.
Driver: Driver code.
OK, here's the code :)
ChessPiece
Movement Interface:
PieceFactory
Knight class
PhoneChess entrant class
Driver Code:
这可以在 O(log N) 内完成。将键盘及其上可能的移动视为一个图G(V, E),其中顶点是可用的数字,边表示哪些数字可以跟随哪个数字。现在,对于每个输出位置i,我们可以形成一个向量Paths(i),其中包含每个顶点可以到达的不同路径的数量。现在很容易看出给定位置i和数字v,它可以到达的可能路径是可能到达的前面数字的不同路径的总和,或者Paths(i)[v] = sum(Paths(i-1)[v2] * (1 if (v,v2) in E else 0) for v2 in V )。现在,这是前面向量的每个位置乘以邻接矩阵列中的相应位置的总和。因此我们可以将其简化为Paths(i) = Paths(i-1) · A,其中A是图的邻接矩阵。摆脱递归并利用矩阵乘法的结合性,这变成Paths(i) = Paths(1) · A^(i-1)。我们知道路径(1):我们只有一条路径,到数字1。n
位数字的路径总数是每个数字的路径总和,所以最终算法变成: TotalPaths(n) = sum( [1,0,0,0,0,0,0,0,0,0] · A^(n-1) )
可以计算幂通过在 O(log(n)) 时间内进行平方,给定常数时间乘法,否则 O(M(n) * log(n)) 其中 M( n) 是您最喜欢的 n 位数字的任意精度乘法算法的复杂度。
This can be done in O(log N). Consider the keypad and the possible moves on it as a graph G(V, E) where vertices are the available digits and edges say which digits can follow which. Now for each output position i we can form a vector Paths(i) containing the number of different paths each vertex can be reached in. Now it's pretty easy to see that for a given position i and digit v, the possible paths that it can be reached through is the sum of the different paths that possible preceding digits could be reached through, or Paths(i)[v] = sum(Paths(i-1)[v2] * (1 if (v,v2) in E else 0) for v2 in V ). Now, this is taking the sum of each position the preceding vector times a corresponding position in a column of the adjacency matrix. So we can simplify this as Paths(i) = Paths(i-1) · A, where A is the adjacency matrix of the graph. Getting rid of the recursion and taking advantage of associativity of matrix multiplication, this becomes Paths(i) = Paths(1) · A^(i-1). We know Paths(1): we have only one path, to the digit 1.
The total number of paths for an n digit number is the sum of the paths for each digit, so the final algorithm becomes: TotalPaths(n) = sum( [1,0,0,0,0,0,0,0,0,0] · A^(n-1) )
The exponentiation can be calculated via squaring in O(log(n)) time, given constant time multiplies, otherwise O(M(n) * log(n)) where M(n) is the complexity of your favorite arbitrary precision multiplication algorithm for n digit numbers.
一个更简单的答案。
庆祝!!
A simpler answer.
celebrate!!
运行时间常数时间解:
Run time constant time solution:
方法返回从 1 开始的 10 位数字的列表。计数再次为 1424。
Method returns list of 10 digit numbers starting with 1. Again the count is 1424.
我不确定我是否错过了什么,但阅读问题的描述后我找到了这个解决方案。它的时间复杂度为 O(n),空间复杂度为 O(1)。
我认为 1 号在拐角处,对吧?在每个角落,您可以移动到其中一侧(从 9 和 3 移动到 4,或从 7 到 1 移动到 6)或“垂直”边之一(从 3 和 1 移动到 8,或从 9 和 7 移动到 2)。因此,角球增加了两个移动:侧向移动和“垂直”移动。这对于所有四个角 (1,3,9,7) 都是如此。
从每一侧,您可以移动到两个角(从 6 移动到 7 和 1,从 4 移动到 9 和 3),也可以到达底部的键 (0)。就这样三个动作。两个角和一个底部。
在底部的键 (0) 上,您可以移动到两侧(4 和 6)。因此,在每一步中,您都会检查先前长度的路径的所有可能结局(即,有多少个结束于角、边、“垂直”或“底部”零键),然后生成新的结局根据前面所说的生成规则进行计数。
如果您从“1”键开始,则从一个可能的角解决方案开始,在每一步中,您计算上一步的角、边、垂直和底部末端的数量,然后应用生成下一个计数的规则。
在纯 JavaScript 代码中。
I'm not sure if I missed something, but reading the description of the problem I came to this solution. It has O(n) time complexity and O(1) space complexity.
I figured that number 1 is at a corner, right? In each corner you can either move to one of the sides (4 from 9 and 3, or 6 from 7 an 1) or one of the 'vertical' sides (8 from 3 and 1, or 2 from 9 and 7). So, corners add two moves: a side move and a 'vertical' move. This is true for all four corners (1,3,9,7).
From each side, you can either move to two corners (7 and 1 from 6, 9 and 3 from 4) or you can reach the bottom key (0). That's three moves. Two corners and one bottom.
On the bottom key (0), you can move to both sides (4 and 6). So, in each step, you check out all possible endings for the path of the previous length (that is, how many ended on a corner, a side, a 'vertical' or the 'bottom' zero key) and then generate new ending counts according to the generation rules stated before.
If you start from the '1' key, you start with one possible corner solution, in each step you count the number of corner, side, vertical and bottom endings of the previous step and then apply the rules to generate the next count.
In plain javascript code.
此问题也可以建模为约束满足问题(简称为CSP)。
我建议使用 Minion 求解器(快速且可扩展),您可以在此处找到它。
建模可能很乏味且耗时(学习曲线陡峭)。
我的建议是使用独立于求解器的建模语言(例如 ESSENCE 并相应地找到转换器。
This problem may be also modelled as a Constraint satisfaction problem (aka CSP for short).
I suggest to use the Minion solver (fast and scalable) that you can find here.
Modelling maybe tedious and time consumming (steep learning curve).
Instead of using Minion language input, my advice is to formulate the model with solver independent modelling language such as ESSENCE and find a converter accordingly.
递归记忆方法:
Recursive memoization approach:
我实现了暴力和动态编程模型
I implemented both brute force and dynamic programming models
Java中的递归函数:
Recursive function in Java: