Imaginez un robot assis dans le coin supérieur gauche d'une grille NxN. Le robot ne peut se déplacer que dans deux directions: à droite et en bas. Combien y a-t-il de chemins possibles pour le robot?
Je pourrais trouver une solution à ce problème sur Google, mais je ne suis pas très clair avec les explications. J'essaie de comprendre clairement la logique sur la façon de résoudre ce problème et de le mettre en œuvre en Java. Toute aide est appréciée.
Mise à jour: Ceci est une question d'entrevue. Pour l'instant, j'essaie d'atteindre l'extrémité inférieure droite et d'imprimer les chemins possibles.
Je ne vois aucune indication d'obstacle dans votre question, alors je suppose qu'il n'y en a pas.
Notez que les robots doivent suivre exactement les étapes 2n
pour atteindre le coin inférieur droit. Ainsi, il ne peut pas faire plus que 2n
mouvements.
Commençons par cette affaire privée: [trouvez tous les chemins vers le coin inférieur droit]
Le robot peut créer exactement les chemins choose(n,2n)
= (2n)!/(n!*n!)
: il n'a qu'à choisir lequel de ces mouvements 2n
sera correct, les autres sont en baisse [et il y a exactement n
d'entre eux].
Pour générer les chemins possibles: générez tous les vecteurs binaires de taille 2n
avec exactement n
1. Les 1 indiqueront les bons mouvements et les 0 les plus bas.
Maintenant développons-le sur tous les chemins:
Vous devez d'abord choisir la longueur du chemin. Il suffit de parcourir toutes les possibilités: 0 <= i <= 2n
, où i
est la longueur du chemin. dans ce chemin, il y a max(0,i-n) <= j <= min(i,n)
étapes correctes .
Pour générer toutes les possibilités, suivez ce pseudo-code:
for each i in [0,2n]:
for each j in [max(0,i-n),min(i,n)]:
print all binary vectors of size i with exactly j bits set to 1
Note1: l'impression de tous les vecteurs binaires de taille i avec j bits définis sur 1 pourrait s'avérer être très longue en calcul. Il est prévu qu'il existe un nombre exponentiel de solutions pour le robot .
Note2: Pour le cas i=2n
, vous obtenez j in [n,n]
, comme nous nous attendions à voir [le cas privé que nous avons décrit ci-dessus].
public static int computePaths(int n){
return recursive(n, 1, 1);
}
public static int recursive(int n, int i, int j){
if( i == n || j == n){
//reach either border, only one path
return 1;
}
return recursive(n, i + 1, j) + recursive(n, i, j + 1);
}
Pour trouver tous les chemins possibles:
toujours en utilisant une méthode récursive. Une variable de chemin est affectée "" au début, puis ajoutez chaque point visité à "chemin". Un chemin possible est formé lorsque vous atteignez le point (n, n), puis ajoutez-le à la liste.
Chaque chemin est désigné par une chaîne, par exemple "(1,1) (2,1) (3,1) (4,1) (4,2) (4,3) (4,4)" . Tous les chemins possibles sont stockés dans une liste de chaînes.
public static List<String> robotPaths(int n){
List<String> pathList = new ArrayList<String>();
getPaths(n, 1,1, "", pathList);
return pathList;
}
public static void getPaths(int n, int i, int j, String path, List<String> pathList){
path += String.format(" (%d,%d)", i , j);
if( i ==n && j == n){ //reach the (n,n) point
pathList.add(path);
}else if( i > n || j > n){//wrong way
return;
}else {
getPaths(n, i +1, j , path, pathList);
getPaths(n, i , j +1, path, pathList);
}
}
https://math.stackexchange.com/questions/104032/finding-points-in-a-grid-with-exactly-k-paths-to-them - regardez ici ma solution. On dirait que c'est exactement ce dont vous avez besoin (oui, les déclarations sont légèrement différentes, mais dans le cas général, elles sont identiques).
Ceci est valable si le robot peut utiliser 4 directions plutôt que 2, mais la solution récursive ci-dessous (en Javascript) fonctionne et j'ai essayé de la rendre aussi lisible que possible:
//first make a function to create the board as an array of arrays
var makeBoard = function(n) {
var board = [];
for (var i = 0; i < n; i++) {
board.Push([]);
for (var j = 0; j < n; j++) {
board[i].Push(false);
}
}
board.togglePiece = function(i, j) {
this[i][j] = !this[i][j];
}
board.hasBeenVisited = function(i, j) {
return !!this[i][j];
}
board.exists = function(i, j) {
return i < n && i > -1 && j < n && j > -1;
}
board.viablePosition = function(i, j) {
return board.exists(i, j) && !board.hasBeenVisited(i,j);
}
return board;
};
var robotPaths = function(n) {
var numPaths = 0;
//call our recursive function (defined below) with a blank board of nxn, with the starting position as (0, 0)
traversePaths(makeBoard(n), 0, 0);
//define the recursive function we'll use
function traversePaths(board, i, j) {
//BASE CASE: if reached (n - 1, n - 1), count as solution and stop doing work
if (i === (n - 1) && j === (n - 1)) {
numPaths++;
return;
}
//mark the current position as having been visited. Doing this after the check for BASE CASE because you don't want to turn the target position (i.e. when you've found a solution) to true or else future paths will see it as an unviable position
board.togglePiece(i, j);
//RECURSIVE CASE: if next point is a viable position, go there and make the same decision
//go right if possible
if (board.viablePosition(i, j + 1)) {
traversePaths(board, i, j + 1);
}
//go left if possible
if (board.viablePosition(i, j - 1)) {
traversePaths(board, i, j - 1);
}
//go down if possible
if (board.viablePosition(i + 1, j)) {
traversePaths(board, i + 1, j);
}
//go up if possible
if (board.viablePosition(i - 1, j)) {
traversePaths(board, i - 1, j);
}
//reset the board back to the way you found it after you've gone forward so that other paths can see it as a viable position for their routes
board.togglePiece(i, j);
}
return numPaths;
};
Une version plus propre:
var robotPaths = function(n, board, i, j) {
board = board || makeBoard(n),
i = i || 0,
j = j || 0;
// If current cell has been visited on this path or doesn't exist, can't go there, so do nothing (no need to return since there are no more recursive calls below this)
if (!board.viablePosition(i, j)) return 0;
// If reached the end, add to numPaths and stop recursing
if (i === (n - 1) && j === (n - 1)) return 1;
// Mark current cell as having been visited for this path
board.togglePiece(i, j);
// Check each of the four possible directions
var numPaths = robotPaths(n, board, i + 1, j) + robotPaths(n, board, i - 1, j) + robotPaths(n, board, i, j + 1) + robotPaths(n, board, i, j - 1);
// Reset current cell so other paths can go there (since board is a pointer to an array that every path is accessing)
board.togglePiece(i, j);
return numPaths;
}
Alors:
robotPaths(5); //returns 8512
Voici la version c # (juste pour référence) pour trouver des chemins uniques (notez ici la version qui retourne le nombre de chemins en utilisant la programmation dynamique (mémorisation - lazy) - Calcul du nombre de déplacements du coin supérieur gauche au coin inférieur droit avec le déplacement dans n’importe lequel direction ) (vous pouvez vous référer à mon blog pour plus de détails: http://codingworkout.blogspot.com/2014/08/robot-in-grid-unique-paths.html )
Tuple<int, int>[][] GetUniquePaths(int N)
{
var r = this.GetUniquePaths(1, 1, N);
return r;
}
private Tuple<int, int>[][] GetUniquePaths(int row, int column, int N)
{
if ((row == N) && (column == N))
{
var r = new Tuple<int, int>[1][];
r[0] = new Tuple<int, int>[] { new Tuple<int,int>(row, column) };
return r;
}
if ((row > N) || (column > N))
{
return new Tuple<int, int>[0][];
}
var uniquePathsByMovingDown = this.GetUniquePaths(row + 1, column, N);
var uniquePathsByMovingRight = this.GetUniquePaths(row, column + 1, N);
List<Tuple<int, int>[]> paths = this.MergePaths(uniquePathsByMovingDown,
row, column).ToList();
paths.AddRange(this.MergePaths(uniquePathsByMovingRight, row, column));
return paths.ToArray();
}
où
private Tuple<int, int>[][] MergePaths(Tuple<int, int>[][] paths,
int row, int column)
{
Tuple<int, int>[][] mergedPaths = new Tuple<int, int>[paths.Length][];
if (paths.Length > 0)
{
Assert.IsTrue(paths.All(p => p.Length > 0));
for (int i = 0; i < paths.Length; i++)
{
List<Tuple<int, int>> mergedPath = new List<Tuple<int, int>>();
mergedPath.Add(new Tuple<int, int>(row, column));
mergedPath.AddRange(paths[i]);
mergedPaths[i] = mergedPath.ToArray();
}
}
return mergedPaths;
}
Tests unitaires
[TestCategory(Constants.DynamicProgramming)]
public void RobotInGridTests()
{
int p = this.GetNumberOfUniquePaths(3);
Assert.AreEqual(p, 6);
int p1 = this.GetUniquePaths_DP_Memoization_Lazy(3);
Assert.AreEqual(p, p1);
var p2 = this.GetUniquePaths(3);
Assert.AreEqual(p1, p2.Length);
foreach (var path in p2)
{
Debug.WriteLine("===================================================================");
foreach (Tuple<int, int> t in path)
{
Debug.Write(string.Format("({0}, {1}), ", t.Item1, t.Item2));
}
}
p = this.GetNumberOfUniquePaths(4);
Assert.AreEqual(p, 20);
p1 = this.GetUniquePaths_DP_Memoization_Lazy(4);
Assert.AreEqual(p, p1);
p2 = this.GetUniquePaths(4);
Assert.AreEqual(p1, p2.Length);
foreach (var path in p2)
{
Debug.WriteLine("===================================================================");
foreach (Tuple<int, int> t in path)
{
Debug.Write(string.Format("({0}, {1}), ", t.Item1, t.Item2));
}
}
}
Vous trouverez ci-dessous le code en Java pour compter tous les chemins possibles du coin supérieur gauche au coin inférieur droit d'une matrice NXN.
public class paths_in_matrix {
/**
* @param args
*/
static int n=5;
private boolean[][] board=new boolean[n][n];
int numPaths=0;
paths_in_matrix(){
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
board[i][j]=false;
}
}
}
private void togglePiece(int i,int j){
this.board[i][j]=!this.board[i][j];
}
private boolean hasBeenVisited(int i,int j){
return this.board[i][j];
}
private boolean exists(int i,int j){
return i < n && i > -1 && j < n && j > -1;
}
private boolean viablePosition(int i,int j){
return exists(i, j) && !hasBeenVisited(i,j);
}
private void traversePaths(int i,int j){
//BASE CASE: if reached (n - 1, n - 1), count as path and stop.
if (i == (n - 1) && j == (n - 1)) {
this.numPaths++;
return;
}
this.togglePiece(i, j);
//RECURSIVE CASE: if next point is a viable position, go there and make the same decision
//go right if possible
if (this.viablePosition(i, j + 1)) {
traversePaths(i, j + 1);
}
//go left if possible
if (this.viablePosition(i, j - 1)) {
traversePaths( i, j - 1);
}
//go down if possible
if (this.viablePosition(i + 1, j)) {
traversePaths( i + 1, j);
}
//go up if possible
if (this.viablePosition(i - 1, j)) {
traversePaths(i - 1, j);
}
//reset the board back to the way you found it after you've gone forward so that other paths can see it as a viable position for their routes
this.togglePiece(i, j);
}
private int robotPaths(){
traversePaths(0,0);
return this.numPaths;
}
public static void main(String[] args) {
paths_in_matrix mat=new paths_in_matrix();
System.out.println(mat.robotPaths());
}
}
Voici une implémentation complète qui fonctionne pour les grilles rectangulaires et carrées. Je vous laisse découvrir comment prendre soin de l'excès "=>" à la fin de chaque chemin.
import Java.util.Arraylist;
public class PrintPath
{
static ArrayList<String> paths = new ArrayList<String>();
public static long getUnique(int m, int n, int i, int j, String pathlist)
{
pathlist += ("(" + i + ", " + (j) + ") => ");
if(m == i && n == j)
{
paths.add(pathlist);
}
if( i > m || j > n)
{
return 0;
}
return getUnique(m, n, i+1, j, pathlist)+getUnique(m, n, i, j+1, pathlist);
}
public static void printPaths()
{
int count = 1;
System.out.println("There are "+paths.size() + " unique paths: \n");
for (int i = paths.size()-1; i>=0; i--)
{
System.out.println( "path " + count + ": " + paths.get(i));
count++;
}
}
public static void main(String args[])
{
final int start_Point = 1;
int grid_Height = 2;
int grid_Width = 2;
getUnique(grid_Height, grid_Width, start_Point, start_Point, "");
printPaths();
}
}
Si vous avez juste besoin d'un nombre de chemins valides:
Supposons que vous avez une matrice n * m et que vous définissez toutes les cellules sur zéro et les cellules "non conformes" sur -1.
Vous pouvez alors résoudre le problème avec la programmation dynamique:
// a is a matrix with 0s and -1s
// n, m are the dimensions
// M is 10^9-7 incase you have a large matrix
if (a[0][0] == 0) a[0][0] = 1;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (a[i][j] == -1) continue;
if (i > 0) a[i][j] = (a[i][j] + max(a[i-1][j], 0LL)) % M;
if (j > 0) a[i][j] = (a[i][j] + max(a[i][j-1], 0LL)) % M;
}
}
// answer at lower right corner
cout << a[n-1][m-1];
Rapide, sans récursion ni structure de données volumineuse.
NOTE: ceci a été supprimé parce qu’il est en double, mais comme c’est le meilleur fil de discussion sur ce sujet, j’ai supprimé ma réponse ailleurs et l’ajouterai ici.
Scénario:
1. Imaginez qu'il existe une matrice indexée à zéro NxN.
2. La position initiale du robot est le coin supérieur gauche, à savoir (N-1, N-1).
3. Le robot veut atteindre le coin inférieur droit, à savoir (0,0)
Solution:
-- Dans toute solution possible, le robot déplacera N étapes de droits et N étapes vers le bas pour atteindre (0,0), ou nous pouvons dire que le robot initial est autorisé à déplacer N étapes de droits et N étapes.
-- - Chaque fois que le robot se déplace bien, nous réduisons de 1 le nombre restant de pas corrects. Il en va de même pour les mouvements descendants.
-- À chaque position (sauf à la limite, où il n’aura qu’une seule option), le robot a deux options: l’une peut être baissée ou l’autre, c’est correct.
-- Il se terminera lorsque le robot n'aura plus rien à faire.
** Sous le code ont également une méthode de pilote main (), vous pouvez changer la valeur de N. N peut être> = 1
public class RobotPaths {
public static int robotPaths(int down, int right, String path)
{
path = path+ down +","+ right +" ";
if(down==0 && right==0)
{
System.out.println(path);
return 1;
}
int counter = 0;
if(down==0)
counter = robotPaths(down, right-1, path);
else if(right==0)
counter = robotPaths(down-1, right, path);
else
counter = robotPaths(down, right-1, path) + robotPaths(down-1, right, path);
return counter;
}
public static void main(String[] args)
{
int N = 1;
System.out.println("Total possible paths: "+RobotPaths.robotPaths(N-1, N-1, ""));
}
}