一、概述
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好久沒寫,手生了,沒想到這個。不然還是不難的,寫出來又能前進好多名。