【LeetCode】Biweekly Contest 9 : Minimum Knight Moves 象棋中馬的最短路徑

一、概述

LeetCode雙週賽的第二題:輸入一個座標,馬從(0,0)開始走,走到該座標所需要的最短步數。

如圖馬能往八個方向走。我的第一個思路就是DFS,開始沒想到循環問題,所以時間溢出,然後使用二維數組記錄,可以得到一個結果,但不是最終的結果,我得不到最短的路徑。事實證明DFS走不通,應該換別的方法。

另外說一句,這次四道題,第一道和第三道純粹簽到題,第二道也就是這道有一點難,最後一道可能得用DP,然而DP我就沒學過,所以就扔了。只做出來兩道簽到題感覺自己很菜。

二、分析

1、我的方法

就是從0開始,將八個方向編號然後遞歸。

由於棋盤的對稱性,將目標座標全取絕對值也能得到相同的結果,這樣就減少了條件判斷。

遞歸出口我設置爲與目標座標相等或者遞歸座標超過目標座標2格就返回。另外遇到之前走過的各自也返回。

代碼如下:

class Solution {
	int node[300][300] = { 0 };
	int result = 0;
	void DFS(int x, int y, int tx, int ty, int step)
	{
		//cout << " x: "<<x << " y: " << y << "  ";
		if (x == tx&&y == ty)
		{
			result = step;
			return;
		}
		else if (x > tx + 2 || x < tx-2 ||y>ty+2||y<ty-2)
			return;
		else if (node[x+150][y+150] == 1)
			return;
		else
		{
			node[x+150][y+150] = 1;
			for (int i = 0; i<8; i++)
			{
				//cout << i<<" ";
				if (result != 0)
					return;
				switch (i)
				{
				case 0:
					DFS(x + 2, y + 1, tx, ty, step + 1);
					break;
				case 1:
					DFS(x + 1, y + 2, tx, ty, step + 1);
					break;
				case 2:
					DFS(x - 1, y + 2, tx, ty, step + 1);
					break;
				case 3:
					DFS(x - 2, y + 1, tx, ty, step + 1);
					break;
				case 4:
					DFS(x - 2, y - 1, tx, ty, step + 1);
					break;
				case 5:
					DFS(x - 1, y - 2, tx, ty, step + 1);
					break;
				case 6:
					DFS(x + 1, y - 2, tx, ty, step + 1);
					break;
				case 7:
					DFS(x + 2, y - 1, tx, ty, step + 1);
					break;
				}

			}
		}
	}
public:
	int minKnightMoves(int x, int y) {
		x = abs(x);
		y = abs(y);
        int x_=0,y_=0;
        int step=0;
        while(x_<x)
        {
            x_=x_+2;
            y_=y_+1;
            step++;
        }
		DFS(x_, y_, x, y,step);
		return result;
	}
};

這種方法只能找到結果,沒法找到最優結果。

2、正確的方法

第一個是uwi的方法,實話實說,我沒看懂:

class Solution {
	    public int minKnightMoves(int x, int y) {
	        return (int)knightDistance(x, y);
	    }
		public long knightDistance(long r, long c)
		{
			r = Math.abs(r); c = Math.abs(c);
			if(r + c == 0)return 0;
			if(r + c == 1)return 3;
			if(r == 2 && c == 2)return 4;
			
			long step = Math.max((r+1)/2, (c+1)/2);
			step = Math.max(step, (r+c+2)/3);
			step += (step^r^c)&1;
			return step;
		}
	}

前面幾個判斷看懂了,就是從0,0到2,2要四步,到0,1要三步。之後的處理無法理解。於是看另外一個老哥的代碼:

int dx[] = {-2, -2, -1, -1, 1, 1, 2, 2};
int dy[] = {-1, 1, -2, 2, -2, 2, -1, 1};

int H[888][888];

class Solution {
public:
    using pii = pair<int, int>;
    int minKnightMoves(int x, int y) {
        int xt = abs(x);
        int yt = abs(y);
        for (int i = -321; i <= 321; ++ i)
            for (int j = -321; j <= 321; ++ j)
                H[i+400][j+400] = -1;
        H[0+400][0+400] = 0;
        queue<pii> Q;
        Q.push({0, 0});
        while (!Q.empty())
        {
            auto [x, y] = Q.front();
            Q.pop();
            if (x == xt && y == yt) return H[x+400][y+400];
            for (int k = 0; k < 8; ++ k)
            {
                int tx = x+dx[k], ty = y+dy[k];
                if (H[tx+400][ty+400] == -1)
                {
                    H[tx+400][ty+400] = H[x+400][y+400]+1;
                    Q.push({tx, ty});
                    if (tx == xt && ty == yt) return H[tx+400][ty+400];
                }
            }
        }
        return -1;
    }
};

這就友好多了。

首先,我們開一個二維數組作爲棋盤,元素賦爲-1,即所有未走過的元素都賦值爲-1。那麼有一個問題,0,0不能在中間,因爲數組下標不能有負數。於是我們需要給輸入的座標到二維數組的下標做一個映射:各加350,讓700*700的棋盤中350,350作爲下標。然後BFS標準寫法。對於這種座標題目,用pair會很方便,這點要學習。

BFS標準寫法是什麼呢?先開隊列,起點進隊,開循環,循環退出條件爲隊列空。拿出隊首元素,開始子循環,子循環中入隊即可。

有一個地方不要混淆,就是隊列中存的座標不帶偏置,到了棋盤上纔要加偏置,棋盤中的元素就是走到這裏要用的步數。

這個算法要是用圖像來表示,類似一朵煙花。第一次循環有8個1,第二次循環8個1周圍各出現8個2......這樣就保證了最短距離,因爲每次本質都是走一步,走一步一定是最短的。

三、總結

BFS好久沒寫,手生了,沒想到這個。不然還是不難的,寫出來又能前進好多名。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章