给出如下所示的电话键盘:
1 2 3
4 5 6
7 8 9
0
从1开始可以形成多少个不同的10位数字?约束是从1位数到下一位数的运动类似于国际象棋比赛中骑士的运动。
例如。如果我们在1那么下一个数字可以是6或8 如果我们在6那么下一个数字可以是1,7或0。
允许重复数字 - 1616161616是有效数字。
是否有多项式时间算法可以解决这个问题?问题要求我们只计算10位数字,而不一定列出数字。
编辑:我尝试将其建模为图形,每个数字都有2或3位数作为其邻居。然后我使用DFS导航到10个节点的深度,然后在每次达到10的深度时增加数字的数量。这显然不是多项式时间。假设每个数字只有2个邻居,则需要至少2 ^ 10次迭代。这里的变量是位数。我采取了例如。 10位数字。它也可以是n位数。
答案 0 :(得分:42)
当然可以在多项式时间内完成。这是dynamic programming或memoization中的一个很好的练习。
假设示例中N(数字位数)等于10。
可以像这样递归地思考:我可以使用从1
开始的10位数构建多少个数字?
答案是
[number of 9-digit numbers starting from 8] +
[number of 9-digit numbers starting from 6].
那么有多少“从8开始的9位数字”?嗯,
[number of 8-digit numbers starting from 1] +
[number of 8-digit numbers starting from 3]
等等。当你得到问题“从X
开始有多少个1位数字时,就达到了基本情况(答案显然是1)。
在复杂性方面,关键的观察是您重复使用先前计算的解决方案。例如,“从3
”开始有多少5位数字的答案,可以在回答“有多少6位数字时使用从8
开始 AND “从4
”开始有多少个6位数字。这种重用使得复杂性从指数变为多项式。
让我们仔细看看动态编程解决方案的复杂性:
此类实施将按以下方式填写矩阵:
num[1][i] = 1, for all 0<=i<=9 -- there are one 1-digit number starting from X.
for digits = 2...N
for from = 0...9
num[digits][from] = num[digits-1][successor 1 of from] +
num[digits-1][successor 2 of from] +
...
num[digits-1][successor K of from]
return num[N][1] -- number of N-digit numbers starting from 1.
该算法一次只填充矩阵一个单元格,矩阵的维数为10 * N,因此在线性时间下运行。
从头顶写下来,如果有任何错别字,请纠正我。
答案 1 :(得分:2)
我决定解决这个问题并尽可能地扩展它。此解决方案允许您:
定义自己的主板(手机垫,国际象棋棋盘等)
定义自己的棋子(Knight,Rook,Bishop等);你必须编写具体的类并从工厂生成它。
通过一些有用的实用方法检索几条信息。
课程如下:
PadNumber:定义电话簿上按钮的类。可以重命名为'Square'代表棋盘广场。
ChessPiece:为所有棋子定义字段的抽象类。
移动:定义移动方法的接口,允许工厂生成碎片。
PieceFactory:生成棋子的工厂类。
Knight:继承自ChessPiece并实现Movement
的具体类PhoneChess:入学课程。
驱动程序:驱动程序代码。
好的,这是代码:)
package PhoneChess;
import java.awt.Point;
public class PadNumber {
private String number = "";
private Point coordinates = null;
public PadNumber(String number, Point coordinates)
{
if(number != null && number.isEmpty()==false)
this.number = number;
else
throw new IllegalArgumentException("Input cannot be null or empty.");
if(coordinates == null || coordinates.x < 0 || coordinates.y < 0)
throw new IllegalArgumentException();
else
this.coordinates = coordinates;
}
public String getNumber()
{
return this.number;
}
public Integer getNumberAsNumber()
{
return Integer.parseInt(this.number);
}
public Point getCoordinates()
{
return this.coordinates;
}
public int getX()
{
return this.coordinates.x;
}
public int getY()
{
return this.coordinates.y;
}
}
ChessPiece
package PhoneChess;
import java.util.HashMap;
import java.util.List;
public abstract class ChessPiece implements Movement {
protected String name = "";
protected HashMap<PadNumber, List<PadNumber>> moves = null;
protected Integer fullNumbers = 0;
protected int[] movesFrom = null;
protected PadNumber[][] thePad = null;
}
运动界面:
package PhoneChess;
import java.util.List;
public interface Movement
{
public Integer findNumbers(PadNumber start, Integer digits);
public abstract boolean canMove(PadNumber from, PadNumber to);
public List<PadNumber> allowedMoves(PadNumber from);
public Integer countAllowedMoves(PadNumber from);
}
PieceFactory
package PhoneChess;
public class PieceFactory
{
public ChessPiece getPiece(String piece, PadNumber[][] thePad)
{
if(thePad == null || thePad.length == 0 || thePad[0].length == 0)
throw new IllegalArgumentException("Invalid pad");
if(piece == null)
throw new IllegalArgumentException("Invalid chess piece");
if(piece.equalsIgnoreCase("Knight"))
return new Knight("Knight", thePad);
else
return null;
}
}
骑士班
package PhoneChess;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
public final class Knight extends ChessPiece implements Movement {
/**Knight movements
* One horizontal, followed by two vertical
* Or
* One vertical, followed by two horizontal
* @param name
*/
public Knight(String name, PadNumber[][] thePad)
{
if(name == null || name.isEmpty() == true)
throw new IllegalArgumentException("Name cannot be null or empty");
this.name = name;
this.thePad = thePad;
this.moves = new HashMap<>();
}
private Integer fullNumbers = null;
@Override
public Integer findNumbers(PadNumber start, Integer digits)
{
if(start == null || "*".equals(start.getNumber()) || "#".equals(start.getNumber()) ) { throw new IllegalArgumentException("Invalid start point"); }
if(start.getNumberAsNumber() == 5) { return 0; } //Consider adding an 'allowSpecialChars' condition
if(digits == 1) { return 1; };
//Init
this.movesFrom = new int[thePad.length * thePad[0].length];
for(int i = 0; i < this.movesFrom.length; i++)
this.movesFrom[i] = -1;
fullNumbers = 0;
findNumbers(start, digits, 1);
return fullNumbers;
}
private void findNumbers(PadNumber start, Integer digits, Integer currentDigits)
{
//Base condition
if(currentDigits == digits)
{
//Reset
currentDigits = 1;
fullNumbers++;
return;
}
if(!this.moves.containsKey(start))
allowedMoves(start);
List<PadNumber> options = this.moves.get(start);
if(options != null)
{
currentDigits++; //More digits to be got
for(PadNumber option : options)
findNumbers(option, digits, currentDigits);
}
}
@Override
public boolean canMove(PadNumber from, PadNumber to)
{
//Is the moves list available?
if(!this.moves.containsKey(from.getNumber()))
{
//No? Process.
allowedMoves(from);
}
if(this.moves.get(from) != null)
{
for(PadNumber option : this.moves.get(from))
{
if(option.getNumber().equals(to.getNumber()))
return true;
}
}
return false;
}
/***
* Overriden method that defines each Piece's movement restrictions.
*/
@Override
public List<PadNumber> allowedMoves(PadNumber from)
{
//First encounter
if(this.moves == null)
this.moves = new HashMap<>();
if(this.moves.containsKey(from))
return this.moves.get(from);
else
{
List<PadNumber> found = new ArrayList<>();
int row = from.getY();//rows
int col = from.getX();//columns
//Cases:
//1. One horizontal move each way followed by two vertical moves each way
if(col-1 >= 0 && row-2 >= 0)//valid
{
if(thePad[row-2][col-1].getNumber().equals("*") == false &&
thePad[row-2][col-1].getNumber().equals("#") == false)
{
found.add(thePad[row-2][col-1]);
this.movesFrom[from.getNumberAsNumber()] = this.movesFrom[from.getNumberAsNumber()] + 1;
}
}
if(col-1 >= 0 && row+2 < thePad.length)//valid
{
if(thePad[row+2][col-1].getNumber().equals("*") == false &&
thePad[row+2][col-1].getNumber().equals("#") == false)
{
found.add(thePad[row+2][col-1]);
this.movesFrom[from.getNumberAsNumber()] = this.movesFrom[from.getNumberAsNumber()] + 1;
}
}
if(col+1 < thePad[0].length && row+2 < thePad.length)//valid
{
if(thePad[row+2][col+1].getNumber().equals("*") == false &&
thePad[row+2][col+1].getNumber().equals("#") == false)
{
found.add(thePad[row+2][col+1]);
this.movesFrom[from.getNumberAsNumber()] = this.movesFrom[from.getNumberAsNumber()] + 1;
}
}
if(col+1 < thePad[0].length && row-2 >= 0)//valid
{
if(thePad[row-2][col+1].getNumber().equals("*") == false &&
thePad[row-2][col+1].getNumber().equals("#") == false)
found.add(thePad[row-2][col+1]);
}
//Case 2. One vertical move each way follow by two horizontal moves each way
if(col-2 >= 0 && row-1 >= 0)
{
if(thePad[row-1][col-2].getNumber().equals("*") == false &&
thePad[row-1][col-2].getNumber().equals("#") == false)
found.add(thePad[row-1][col-2]);
}
if(col-2 >= 0 && row+1 < thePad.length)
{
if(thePad[row+1][col-2].getNumber().equals("*") == false &&
thePad[row+1][col-2].getNumber().equals("#") == false)
found.add(thePad[row+1][col-2]);
}
if(col+2 < thePad[0].length && row-1 >= 0)
{
if(thePad[row-1][col+2].getNumber().equals("*") == false &&
thePad[row-1][col+2].getNumber().equals("#") == false)
found.add(thePad[row-1][col+2]);
}
if(col+2 < thePad[0].length && row+1 < thePad.length)
{
if(thePad[row+1][col+2].getNumber().equals("*") == false &&
thePad[row+1][col+2].getNumber().equals("#") == false)
found.add(thePad[row+1][col+2]);
}
if(found.size() > 0)
{
this.moves.put(from, found);
this.movesFrom[from.getNumberAsNumber()] = found.size();
}
else
{
this.moves.put(from, null); //for example the Knight cannot move from 5 to anywhere
this.movesFrom[from.getNumberAsNumber()] = 0;
}
}
return this.moves.get(from);
}
@Override
public Integer countAllowedMoves(PadNumber from)
{
int start = from.getNumberAsNumber();
if(movesFrom[start] != -1)
return movesFrom[start];
else
{
movesFrom[start] = allowedMoves(from).size();
}
return movesFrom[start];
}
@Override
public String toString()
{
return this.name;
}
}
PhoneChess参赛班
package PhoneChess;
public final class PhoneChess
{
private ChessPiece thePiece = null;
private PieceFactory factory = null;
public ChessPiece ThePiece()
{
return this.thePiece;
}
public PhoneChess(PadNumber[][] thePad, String piece)
{
if(thePad == null || thePad.length == 0 || thePad[0].length == 0)
throw new IllegalArgumentException("Invalid pad");
if(piece == null)
throw new IllegalArgumentException("Invalid chess piece");
this.factory = new PieceFactory();
this.thePiece = this.factory.getPiece(piece, thePad);
}
public Integer findPossibleDigits(PadNumber start, Integer digits)
{
if(digits <= 0)
throw new IllegalArgumentException("Digits cannot be less than or equal to zero");
return thePiece.findNumbers(start, digits);
}
public boolean isValidMove(PadNumber from, PadNumber to)
{
return this.thePiece.canMove(from, to);
}
}
驱动程序代码:
public static void main(String[] args) {
PadNumber[][] thePad = new PadNumber[4][3];
thePad[0][0] = new PadNumber("1", new Point(0,0));
thePad[0][1] = new PadNumber("2", new Point(1,0));
thePad[0][2] = new PadNumber("3",new Point(2,0));
thePad[1][0] = new PadNumber("4",new Point(0,1));
thePad[1][1] = new PadNumber("5",new Point(1,1));
thePad[1][2] = new PadNumber("6", new Point(2,1));
thePad[2][0] = new PadNumber("7", new Point(0,2));
thePad[2][1] = new PadNumber("8", new Point(1,2));
thePad[2][2] = new PadNumber("9", new Point(2,2));
thePad[3][0] = new PadNumber("*", new Point(0,3));
thePad[3][1] = new PadNumber("0", new Point(1,3));
thePad[3][2] = new PadNumber("#", new Point(2,3));
PhoneChess phoneChess = new PhoneChess(thePad, "Knight");
System.out.println(phoneChess.findPossibleDigits(thePad[0][1],4));
}
}
答案 2 :(得分:1)
这可以在O(log N)中完成。将键盘及其可能的移动视为图形 G(V,E),其中顶点是可用数字,边缘表示哪些数字可以跟随哪些数字。现在,对于每个输出位置 i ,我们可以形成一个向量 Paths(i),其中包含每个顶点可以到达的不同路径的数量。现在很容易看出给定的位置 i 和数字 v ,它可以通过的可能路径是可以通过前面的数字到达的不同路径的总和,或者路径(i)[v] =和(路径(i-1)[v2] *(1,如果(否则为0中的(v,v2),则为V)中的v2 。现在,这是将前一个向量的每个位置的总和乘以邻接矩阵的列中的对应位置。因此我们可以将其简化为 Paths(i)= Paths(i-1)·A ,其中 A 是图的邻接矩阵。摆脱递归并利用矩阵乘法的相关性,这变成路径(i)=路径(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 位数的任意精度乘法算法的复杂性。
答案 3 :(得分:1)
更简单的答案。
#include<stdio.h>
int a[10] = {2,2,2,2,3,0,3,2,2,2};
int b[10][3] = {{4,6},{6,8},{7,9},{4,8},{0,3,9},{},{1,7,0},{2,6},{1,3},{2,4}};
int count(int curr,int n)
{
int sum = 0;
if(n==10)
return 1;
else
{
int i = 0;
int val = 0;
for(i = 0; i < a[curr]; i++)
{
val = count(b[curr][i],n+1);
sum += val;
}
return sum;
}
}
int main()
{
int n = 1;
int val = count(1,0);
printf("%d\n",val);
}
庆祝!!
答案 4 :(得分:1)
运行时间常数解决方案:
#include <iostream>
constexpr int notValid(int x, int y) {
return !(( 1 == x && 3 == y ) || //zero on bottom.
( 0 <= x && 3 > x && //1-9
0 <= y && 3 > y ));
}
class Knight {
template<unsigned N > constexpr int move(int x, int y) {
return notValid(x,y)? 0 : jump<N-1>(x,y);
}
template<unsigned N> constexpr int jump( int x, int y ) {
return move<N>(x+1, y-2) +
move<N>(x-1, y-2) +
move<N>(x+1, y+2) +
move<N>(x-1, y+2) +
move<N>(x+2, y+1) +
move<N>(x-2, y+1) +
move<N>(x+2, y-1) +
move<N>(x-2, y-1);
}
public:
template<unsigned N> constexpr int count() {
return move<N-1>(0,1) + move<N-1>(0,2) +
move<N-1>(1,0) + move<N-1>(1,1) + move<N-1>(1,2) +
move<N-1>(2,0) + move<N-1>(2,1) + move<N-1>(2,2);
}
};
template<> constexpr int Knight::move<0>(int x, int y) { return notValid(x,y)? 0 : 1; }
template<> constexpr int Knight::count<0>() { return 0; } //terminal cases.
template<> constexpr int Knight::count<1>() { return 8; }
int main(int argc, char* argv[]) {
static_assert( ( 16 == Knight().count<2>() ), "Fail on test with 2 lenght" ); // prof of performance
static_assert( ( 35 == Knight().count<3>() ), "Fail on test with 3 lenght" );
std::cout<< "Number of valid Knight phones numbers:" << Knight().count<10>() << std::endl;
return 0;
}
答案 5 :(得分:1)
方法返回以1开头的10位数字列表。再次计数为1424。
public ArrayList<String> getList(int digit, int length, String base ){
ArrayList<String> list = new ArrayList<String>();
if(length == 1){
list.add(base);
return list;
}
ArrayList<String> temp;
for(int i : b[digit]){
String newBase = base +i;
list.addAll(getList(i, length -1, newBase ));
}
return list;
}
答案 6 :(得分:1)
我不确定我是否遗漏了一些东西,但是阅读问题的描述我来到了这个解决方案。它具有O(n)时间复杂度和O(1)空间复杂度。
我认为1号位于角落,对吧?在每个角落,您可以移动到其中一个边(4个从9和3,或6从7和1)或其中一个“垂直”边(8个从3和1,或2从9和7)。因此,角落增加了两个动作:侧移和'垂直'移动。所有四个角都是如此(1,3,9,7)。
从两侧,您可以移动到两个角(7和1从6,9和3从4)或者您可以到达底键(0)。那是三招。两个角落和一个底部。
在底部键(0)上,您可以移动到两侧(4和6)。因此,在每个步骤中,您检查前一长度路径的所有可能结尾(即,在拐角,侧面,“垂直”或“底部”零键上结束的数量)然后生成新的结束根据之前陈述的生成规则计算。
如果从“1”键开始,则从一个可能的角点解决方案开始,在每个步骤中计算上一步的角点,侧面,垂直和底部的数量,然后应用规则生成下一个计数。
简单的javascript代码。
function paths(n) {
//Index to 0
var corners = 1;
var verticals = 0;
var bottom = 0;
var sides = 0;
if (n <= 0) {
//No moves possible for paths without length
return 0;
}
for (var i = 1; i < n; i++) {
var previousCorners = corners;
var previousVerticals = verticals;
var previousBottom = bottom;
var previousSides = sides;
sides = 1 * previousCorners + 2 * previousBottom;
verticals = 1 * previousCorners;
bottom = 1 * previousSides;
corners = 2 * previousSides + 2 * previousVerticals;
//console.log("Moves: %d, Length: %d, Sides: %d, Verticals: %d, Bottom: %d, Corners: %d, Total: %d", i, i + 1, sides, verticals, bottom, corners, sides+verticals+bottom+corners);
}
return sides + verticals + bottom + corners;
}
for (var i = 0; i <= 10; i++) {
console.log(paths(i));
}
答案 7 :(得分:0)
此问题也可以建模为Constraint satisfaction problem(简称CSP)。
我建议您使用 Minion 解算器(快速且可扩展),您可以找到here。
建模可能是乏味和时间消耗(陡峭的学习曲线)。
我建议不要使用Minion语言输入,而是使用求解器独立的建模语言(例如ESSENCE)来制定模型,并相应地找到转换器。
答案 8 :(得分:0)
//Both the iterative and recursive with memorize shows count as 1424 for 10 digit numbers starting with 1.
int[][] b = {{4,6},{6,8},{7,9},{4,8},{0,3,9},{},{1,7,0},{2,6},{1,3},{2,4}};
public int countIterative(int digit, int length) {
int[][] matrix = new int[length][10];
for(int dig =0; dig <=9; dig++){
matrix[0][dig] = 1;
}
for(int len = 1; len < length; len++){
for(int dig =0; dig <=9; dig++){
int sum = 0;
for(int i : b[dig]){
sum += matrix[len-1][i];
}
matrix[len][dig] = sum;
}
}
return matrix[length-1][digit];
}
public int count(int index, int length, int[][] matrix ){
int sum = 0;
if(matrix[length-1][index] > 0){
System.out.println("getting value from memoize:"+index + "length:"+ length);
return matrix[length-1][index];
}
if( length == 1){
return 1;
}
for(int i: b[index] ) {
sum += count(i, length-1,matrix);
}
matrix[length-1][index] = sum;
return sum;
}
答案 9 :(得分:0)
递归记忆方法:
vector<vector<int>> lupt = { {4, 6}, {6, 8}, {9, 7}, {4, 8}, {3, 9, 0},
{}, {1,7,0}, {6, 2}, {1, 3}, {2, 4} };
int numPhoneNumbersUtil(int startdigit, int& phonenumberlength, int currCount, map< pair<int,int>,int>& memT)
{
int noOfCombs = 0;
vector<int> enddigits;
auto it = memT.find(make_pair(startdigit,currCount));
if(it != memT.end())
{
noOfCombs = it->second;
return noOfCombs;
}
if(currCount == phonenumberlength)
{
return 1;
}
enddigits = lupt[startdigit];
for(auto it : enddigits)
{
noOfCombs += numPhoneNumbersUtil(it, phonenumberlength, currCount + 1, memT);
}
memT.insert(make_pair(make_pair(startdigit,currCount), noOfCombs));
return memT[make_pair(startdigit,currCount)];
}
int numPhoneNumbers(int startdigit, int phonenumberlength)
{
map<pair<int,int>,int> memT;
int currentCount = 1; //the first digit has already been added
return numPhoneNumbersUtil(startdigit, phonenumberlength, currentCount, memT);
}
答案 10 :(得分:0)
我实现了强力和动态编程模型
import queue
def chess_numbers_bf(start, length):
if length <= 0:
return 0
phone = [[7, 5], [6, 8], [3, 7], [9, 2, 8], [], [6, 9, 0], [1, 5], [0, 2], [3, 1], [5, 3]]
total = 0
q = queue.Queue()
q.put((start, 1))
while not q.empty():
front = q.get()
val = front[0]
len_ = front[1]
if len_ < length:
for elm in phone[val]:
q.put((elm, len_ + 1))
else:
total += 1
return total
def chess_numbers_dp(start, length):
if length <= 0:
return 0
phone = [[7, 5], [6, 8], [3, 7], [9, 2, 8], [], [6, 9, 0], [1, 5], [0, 2], [3, 1], [5, 3]]
memory = {}
def __chess_numbers_dp(s, l):
if (s, l) in memory:
return memory[(s, l)]
elif l == length - 1:
memory[(s, l)] = 1
return 1
else:
total_n_ways = 0
for number in phone[s]:
total_n_ways += __chess_numbers_dp(number, l+1)
memory[(s, l)] = total_n_ways
return total_n_ways
return __chess_numbers_dp(start, 0)
# bf
for i in range(0, 10):
print(i, chess_numbers_bf(3, i))
print('\n')
for i in range(0, 10):
print(i, chess_numbers_bf(9, i))
print('\n')
# dp
for i in range(0, 10):
print(i, chess_numbers_dp(3, i))
print('\n')
# dp
for i in range(0, 10):
print(i, chess_numbers_dp(9, i))
print('\n')
答案 11 :(得分:-2)
Java中的递归函数:
public static int countPhoneNumbers (int n, int r, int c) {
if (outOfBounds(r,c)) {
return 0;
} else {
char button = buttons[r][c];
if (button == '.') {
// visited
return 0;
} else {
buttons[r][c] = '.'; // record this position so don't revisit.
// Count all possible phone numbers with one less digit starting
int result=0;
result = countPhoneNumbers(n-1,r-2,c-1)
+ countPhoneNumbers(n-1,r-2,c+1)
+ countPhoneNumbers(n-1,r+2,c-1)
+ countPhoneNumbers(n-1,r+2,c+1)
+ countPhoneNumbers(n-1,r-1,c-2)
+ countPhoneNumbers(n-1,r-1,c+2)
+ countPhoneNumbers(n-1,r+1,c-2)
+ countPhoneNumbers(n-1,r+1,c+2);
}
buttons[r][c] = button; // Remove record from position.
return result;
}
}
}