【面試】如何找到迷宮出口

leesf發表於2015-09-23

一、問題描述

給出如下的矩陣

1 1 1 1

0 0 0 1

1 9 0 0

1 1 1 1

其中1代表此位置是可以通過的,0代表此位置是不可通過的,要從起點開始,確定是否存在這樣一條路徑,可以從起點找到數字9。也就是找到這樣一條路徑1->1->1 ...... ->9。這個問題是尋找迷宮出口的變形。可以將9看成是迷宮的出口,而給出的開始位置是迷宮的入口,尋找一條路徑。

二、問題求解

當矩陣階數不是太大的時,可以使用遞迴來求解,但是,當矩陣的階數變得很大時,則需要使用另外的方法進行計算,也就是採用棧來進行求解。

下面是兩種解決方法

1.當矩陣階數較小時,採用遞迴進行求解,這種方式只是用來開拓思路,解此問題存在一定的侷限性。

原始碼如下:

import java.util.Scanner;
public class MatrixPath {
	public static void main(String[] args) {
		Scanner scan = new Scanner(System.in);
		String line = scan.nextLine();	//輸入行和列
		String[] lines = line.split("\\s+");//分離行和列
		int row = Integer.parseInt(lines[0]);
		int col = Integer.parseInt(lines[1]);
		
		int[][] matrix = new int[row + 2][col + 2];	//將外層包圍一層0元素
		footPrints = new boolean[row + 2][col + 2];
		endPrints = new boolean[row + 2][col + 2];  
		
		for(int i = 0; i < row; i++) {	//初始化陣列元素
			line = scan.nextLine();
			lines = line.split("\\s");
			for(int j = 0; j < lines.length; j++) {
				matrix[i + 1][j + 1] = Integer.parseInt(lines[j]);
			}
		}
		
		scan.close();
		
		int startX = 1;	//入口橫座標
		int startY = 1;	//入口縱座標
		
		System.out.println(exsitPath(startX, startY, row, col, matrix) + " ");
	}
	
	public static boolean exsitPath(int x, int y, int row, int col, int[][] matrix) {
		if(x < 0 || y < 0 || x >= row || y >= col)
			return false;
		if(matrix[x][y] == 9)
			return true;
		if(matrix[x][y] == 0)
			return false;
		if(exsitPath(x + 1, y, row, col, matrix) || exsitPath(y - 1, x, row, col, matrix) || exsitPath(x, y - 1, row, col, matrix) || exsitPath(x, y + 1, row, col, matrix))
			return true;
		return false;
	}
}

 測試用例:

3 3
1 1 1
1 9 1
0 1 1

輸出結果為:true

測試用例:

4 4
1 1 1 1
0 0 0 1
0 0 1 1
9 1 1 1

輸出結果:java.lang.StackOverflowError,堆疊溢位

 

2.使用遞迴的方法要選擇好測試用例,很可能因為測試用例的選取不恰當而造成堆疊溢位。遞迴只是為了開拓思路而已,下面使用棧結構來解決堆疊溢位的問題。

原始碼如下:

package com.leesf.Main;

import java.util.ArrayList;
import java.util.Scanner;

class Position {//位置資訊
    private int x;
    private int y;
    
    public Position(int x, int y) {
        this.x = x;
        this.y = y;
    }
    
    public int getX() {
        return x;
    }
    
    public int getY() {
        return y;
    }
}

class Item {//棧中元素型別
    private int order;    //第幾步
    private Position seat;    //位置
    private int value;    //對應的值
    private int di;    //方位
    
    public Item(int order, Position seat, int value, int di) {
        this.order = order;
        this.seat = seat;
        this.value = value;
        this.di = di;
    }
    
    public int getOrder() {
        return order;
    }
    
    public Position getSeat() {
        return seat;
    }
    
    public int getDi() {
        return di;
    }
    
    public int getValue() {
        return value;
    }
    
    public void setDi(int di) {
        this.di = di;
    }
    
}

class MyStack<T> {//定義棧結構
    private static int index = -1;
    private ArrayList<T> array;
    public MyStack() {
        array = new ArrayList<T>();
    }
    
    public void push(T item) {    
        array.add(item);
        index++;
    }
    
    public T pop() {
        T result = array.get(index);
        array.remove(index);
        index--;
        return result;
    }
    
    public boolean isEmpty() {
        if(index != -1)
            return false;
        else 
            return true;
    }
}

public class MatrixPath {
    private static boolean[][] footPrints;    //留下腳印
    private static boolean[][] endPrints;    //留下不能通過印記
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        String line = scan.nextLine();    //輸入行和列
        String[] lines = line.split("\\s+");//分離行和列
        int row = Integer.parseInt(lines[0]);
        int col = Integer.parseInt(lines[1]);
        
        int[][] matrix = new int[row + 2][col + 2];    //將外層包圍一層0元素
        footPrints = new boolean[row + 2][col + 2];
        endPrints = new boolean[row + 2][col + 2];  
        
        for(int i = 0; i < row; i++) {    //初始化陣列元素
            line = scan.nextLine();
            lines = line.split("\\s");
            for(int j = 0; j < lines.length; j++) {
                matrix[i + 1][j + 1] = Integer.parseInt(lines[j]);
            }
        }
        
        scan.close();
        
        int startX = 1;    //入口橫座標
        int startY = 1;    //入口縱座標
        
        System.out.println(exsitPath(startX, startY, row, col, matrix) + " ");

        //System.out.println(exsitPath(startX, startY, matrix) + " ");    
    }
    
    //判斷是否存在路徑
    public static boolean exsitPath(int x, int y, int[][] matrix) {
        
        MyStack<Item> stack = new MyStack<Item>();
        //當前的位置
        Position curPos = new Position(x, y);
        //開始第一步
        int curStep = 1;
       
        do {
            if(pass(curPos, matrix)) { //該位置是可通過的
                System.out.println("x - y = " + curPos.getX() + " - " + curPos.getY());
                footPrint(curPos);//留下腳印
                Item item = new Item(curStep, curPos, matrix[curPos.getX()][curPos.getY()], 1);
                stack.push(item);//將該可通位置放入棧中
                if(item.getValue() == 9) {//如果已經找到目標,則返回
                    System.out.println("總共花費了" + curStep + "步, 找到目標");
                    return true;
                }
                curPos = nextPos(curPos, 1);//試探下一步,即東邊的,但是不放入棧中
                curStep++;//步數加1
                
            } else {//改位置不可通過
                if(!stack.isEmpty()) {//棧不為空
                    Item item = stack.pop();//出棧
                    while(item.getDi() == 4 && !stack.isEmpty()) {//留下了腳印的個數加上不可通過的個數之和為4,進行出棧處理
                        markPrint(item.getSeat());//留下不可通過印記並進行出棧處理
                        item = stack.pop();//出棧
                    }
                    if(item.getDi() < 4) {//四個方向還未試探完
                        item.setDi(item.getDi() + 1);//換下一個方向進行試探
                        stack.push(item);//入棧
                        curPos = nextPos(item.getSeat(), item.getDi());//試探下一個位置
                    }
                }
            }
        } while(!stack.isEmpty());    
        System.out.println("總共花費了" + curStep + "步,沒有找到目標");
        return false;
    }
    
    public static boolean pass(Position curPos, int[][] matrix) {
        if(matrix[curPos.getX()][curPos.getY()] != 0 && endPrints[curPos.getX()][curPos.getY()] == false && footPrints[curPos.getX()][curPos.getY()] == false)
            return true;
        else
            return false;
    }
    
    public static void footPrint(Position curPos) {
        footPrints[curPos.getX()][curPos.getY()] = true;
    }
    
    public static Position nextPos(Position curPos, int di) {
        int x = curPos.getX();
        int y = curPos.getY();
        switch(di) {
        case 1: {
            y = curPos.getY() + 1; //東
        }
        break;
        case 2: {
            x = curPos.getX() + 1; //南
        }
        break;
        case 3: {
            y = curPos.getY() - 1; //西
        }
        break;
        case 4: {
            x = curPos.getX() - 1; //北
        }
        break;
        
        }
        
        return new Position(x, y);
    }
    
    public static void markPrint(Position seat) {
        endPrints[seat.getX()][seat.getY()] = true;
    }
}

  

測試用例:

4 4
1 1 1 1
0 0 0 1
0 0 1 1
9 1 1 1

輸出結果:

x - y = 1 - 1
x - y = 1 - 2
x - y = 1 - 3
x - y = 1 - 4
x - y = 2 - 4
x - y = 3 - 4
x - y = 4 - 4
x - y = 4 - 3
x - y = 4 - 2
x - y = 4 - 1
總共花費了10步, 找到目標
true

 

測試用例:

8 8
1 1 0 0 0 0 1 1
1 1 0 0 0 0 0 0
0 1 1 1 1 0 0 0
1 1 1 1 0 0 0 0
0 1 0 0 1 1 1 1
0 1 1 9 0 0 0 0
0 0 0 0 0 0 0 0
1 0 1 0 1 1 1 1

輸出結果:

x - y = 1 - 1
x - y = 1 - 2
x - y = 2 - 2
x - y = 3 - 2
x - y = 3 - 3
x - y = 3 - 4
x - y = 3 - 5
x - y = 4 - 4
x - y = 4 - 3
x - y = 4 - 2
x - y = 5 - 2
x - y = 6 - 2
x - y = 6 - 3
x - y = 6 - 4
總共花費了14,步, 找到目標
true

測試用例:

4 4
1 0 0 1
1 1 1 1
0 0 1 0
1 1 1 1

輸出結果:


x - y = 1 - 1
x - y = 2 - 1
x - y = 2 - 2
x - y = 2 - 3
x - y = 2 - 4
x - y = 1 - 4
x - y = 3 - 3
x - y = 4 - 3
x - y = 4 - 4
x - y = 4 - 2
x - y = 4 - 1
總共花費了12步,沒有找到目標
false

 

三、總結

  兩種方法完成了問題的解決,當然,遞迴的方法只是用來擴充套件思路,其實,使用遞迴併且記錄之前已經遍歷到一些資訊也是可以完成問題的解答,這就涉及到了動態規劃。這就交給讀者去完成啦。

  好了,謝謝各位觀看,我們下期再會。

 

相關文章